From a7db5748622e1b322b67fbbd72aed4e6efff7690 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 19 Jul 2013 10:50:32 -0400
Subject: [PATCH] Implemented optional page caching

---
 src/main/java/com/gitblit/wicket/pages/ReflogPage.java       |    3 
 src/main/java/com/gitblit/wicket/pages/MarkdownPage.java     |    3 
 src/main/java/com/gitblit/wicket/pages/GitSearchPage.java    |    3 
 src/main/java/WEB-INF/web.xml                                |    2 
 src/main/java/com/gitblit/wicket/pages/TagsPage.java         |    3 
 src/main/java/com/gitblit/wicket/GitBlitWebApp.java          |   18 ++
 src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java     |    3 
 src/main/distrib/data/gitblit.properties                     |    7 
 src/main/java/com/gitblit/wicket/CacheControl.java           |   40 +++++
 src/main/java/com/gitblit/wicket/pages/BlamePage.java        |    3 
 src/main/java/com/gitblit/PagesServlet.java                  |    3 
 src/main/java/com/gitblit/wicket/pages/BasePage.java         |   67 ++++++++
 src/main/java/com/gitblit/wicket/pages/DocsPage.java         |    3 
 src/main/java/com/gitblit/wicket/pages/OverviewPage.java     |    3 
 src/main/java/com/gitblit/wicket/pages/TagPage.java          |    3 
 src/main/java/com/gitblit/wicket/pages/LogPage.java          |    3 
 src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java  |    3 
 src/main/java/com/gitblit/wicket/pages/HistoryPage.java      |    3 
 src/main/java/com/gitblit/wicket/pages/SummaryPage.java      |    3 
 src/main/java/com/gitblit/LogoServlet.java                   |    1 
 src/main/java/com/gitblit/wicket/pages/BranchesPage.java     |    3 
 src/main/java/com/gitblit/wicket/pages/ProjectPage.java      |   23 ++
 src/main/java/com/gitblit/wicket/pages/RepositoryPage.java   |   26 +++
 src/main/java/com/gitblit/wicket/pages/TreePage.java         |    3 
 src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java |    3 
 src/main/java/com/gitblit/GitBlit.java                       |   27 +++
 src/main/java/com/gitblit/wicket/GitblitWicketFilter.java    |  145 ++++++++++++++++++
 src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java   |    8 
 src/main/java/com/gitblit/wicket/pages/PatchPage.java        |    3 
 src/main/java/com/gitblit/wicket/pages/BlobPage.java         |    3 
 src/main/java/com/gitblit/wicket/pages/ActivityPage.java     |    4 
 releases.moxie                                               |    6 
 src/main/java/com/gitblit/wicket/pages/CommitPage.java       |    3 
 src/main/java/com/gitblit/wicket/pages/MetricsPage.java      |    3 
 34 files changed, 428 insertions(+), 9 deletions(-)

diff --git a/releases.moxie b/releases.moxie
index 258435d..49149a8 100644
--- a/releases.moxie
+++ b/releases.moxie
@@ -20,14 +20,18 @@
     changes:
 	- updated Chinese translation
 	- updated Dutch translation
-    additions: ~
+    additions:
+	- Added optional browser-side page caching using Last-Modified and Cache-Control for the dashboard, activity, project, and several repository pages
     dependencyChanges: ~
+	settings:
+	- { name: 'web.pageCacheExpires', defaultValue: 0 }
     contributors:
 	- Rainer Alföldi 
 	- Liyu Wang
 	- Jeroen Baten
 	- James Moger
 	- Stardrad Yin
+	- Chad Horohoe
 }
 
 #
diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties
index f6ed383..1ee6d80 100644
--- a/src/main/distrib/data/gitblit.properties
+++ b/src/main/distrib/data/gitblit.properties
@@ -616,6 +616,13 @@
 # BASEFOLDER
 web.robots.txt = ${baseFolder}/robots.txt
 
+# The number of minutes to cache a page in the browser since the last request.
+# The default value is 0 minutes.  A value <= 0 disables all page caching which
+# is the default behavior for Gitblit <= 1.3.0.
+#
+# SINCE 1.3.1
+web.pageCacheExpires = 0
+
 # If true, the web ui layout will respond and adapt to the browser's dimensions.
 # if false, the web ui will use a 940px fixed-width layout.
 # http://twitter.github.com/bootstrap/scaffolding.html#responsive
diff --git a/src/main/java/WEB-INF/web.xml b/src/main/java/WEB-INF/web.xml
index bf12166..cf71465 100644
--- a/src/main/java/WEB-INF/web.xml
+++ b/src/main/java/WEB-INF/web.xml
@@ -256,7 +256,7 @@
     <filter>
         <filter-name>wicketFilter</filter-name>
         <filter-class>
-            org.apache.wicket.protocol.http.WicketFilter
+            com.gitblit.wicket.GitblitWicketFilter
         </filter-class>
         <init-param>
             <param-name>applicationClassName</param-name>
diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java
index 35ef770..efdaad1 100644
--- a/src/main/java/com/gitblit/GitBlit.java
+++ b/src/main/java/com/gitblit/GitBlit.java
@@ -232,6 +232,33 @@
 		}
 		return gitblit;
 	}
+	
+	/**
+	 * Returns the boot date of the Gitblit server.
+	 * 
+	 * @return the boot date of Gitblit
+	 */
+	public static Date getBootDate() {
+		return self().serverStatus.bootDate;
+	}
+	
+	/**
+	 * Returns the most recent change date of any repository served by Gitblit.
+	 * 
+	 * @return a date
+	 */
+	public static Date getLastActivityDate() {
+		Date date = null;
+		for (String name : self().getRepositoryList()) {
+			Repository r = self().getRepository(name);
+			Date lastChange = JGitUtils.getLastChange(r).when;
+			r.close();
+			if (lastChange != null && (date == null || lastChange.after(date))) {
+				date = lastChange;
+			}
+		}
+		return date;
+	}
 
 	/**
 	 * Determine if this is the GO variant of Gitblit.
diff --git a/src/main/java/com/gitblit/LogoServlet.java b/src/main/java/com/gitblit/LogoServlet.java
index c8820ed..823e464 100644
--- a/src/main/java/com/gitblit/LogoServlet.java
+++ b/src/main/java/com/gitblit/LogoServlet.java
@@ -76,6 +76,7 @@
 				contentType = "image/png";
 			}
 			response.setContentType(contentType);
+			response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
 			OutputStream os = response.getOutputStream();
 			byte[] buf = new byte[4096];
 			int bytesRead = is.read(buf);
diff --git a/src/main/java/com/gitblit/PagesServlet.java b/src/main/java/com/gitblit/PagesServlet.java
index 9516b35..fc71bc5 100644
--- a/src/main/java/com/gitblit/PagesServlet.java
+++ b/src/main/java/com/gitblit/PagesServlet.java
@@ -141,7 +141,6 @@
 				r.close();
 				return;
 			}
-			response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime());
 
 			String [] encodings = GitBlit.getEncodings();
 
@@ -226,6 +225,8 @@
 
 			try {
 				// output the content
+				response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
+				response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime());
 				response.getOutputStream().write(content);
 				response.flushBuffer();
 			} catch (Throwable t) {
diff --git a/src/main/java/com/gitblit/wicket/CacheControl.java b/src/main/java/com/gitblit/wicket/CacheControl.java
new file mode 100644
index 0000000..f72fe3a
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/CacheControl.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Page attribute to control what date as last-modified for the browser cache.
+ * 
+ * http://betterexplained.com/articles/how-to-optimize-your-site-with-http-caching
+ * https://developers.google.com/speed/docs/best-practices/caching
+ * 
+ * @author James Moger
+ *
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CacheControl {
+	
+	public static enum LastModified {
+		BOOT, ACTIVITY, PROJECT, REPOSITORY, COMMIT, NONE
+	}
+	
+	LastModified value() default LastModified.NONE;
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
index cdae093..74ccac7 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
@@ -15,7 +15,9 @@
  */
 package com.gitblit.wicket;
 
+import java.util.HashMap;
 import java.util.Locale;
+import java.util.Map;
 
 import org.apache.wicket.Application;
 import org.apache.wicket.Page;
@@ -70,6 +72,8 @@
 public class GitBlitWebApp extends WebApplication {
 
 	public final static Class<? extends BasePage> HOME_PAGE_CLASS = MyDashboardPage.class;
+	
+	private final Map<String, CacheControl> cacheablePages = new HashMap<String, CacheControl>();
 	
 	@Override
 	public void init() {
@@ -149,6 +153,12 @@
 			parameters = new String[] {};
 		}
 		mount(new GitblitParamUrlCodingStrategy(location, clazz, parameters));
+		
+		// map the mount point to the cache control definition 
+		if (clazz.isAnnotationPresent(CacheControl.class)) {
+			CacheControl cacheControl = clazz.getAnnotation(CacheControl.class);
+			cacheablePages.put(location.substring(1), cacheControl);
+		}
 	}
 
 	@Override
@@ -156,6 +166,14 @@
 		return HOME_PAGE_CLASS;
 	}
 	
+	public boolean isCacheablePage(String mountPoint) {
+		return cacheablePages.containsKey(mountPoint);
+	}
+
+	public CacheControl getCacheControl(String mountPoint) {
+		return cacheablePages.get(mountPoint);
+	}
+
 	@Override
 	public final Session newSession(Request request, Response response) {
 		GitBlitWebSession gitBlitWebSession = new GitBlitWebSession(request);
diff --git a/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java b/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java
new file mode 100644
index 0000000..f46c51e
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket;
+
+import java.util.Date;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.protocol.http.WicketFilter;
+import org.apache.wicket.util.string.Strings;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * 
+ * Customization of the WicketFilter to allow smart browser-side caching of
+ * some pages.
+ * 
+ * @author James Moger
+ *
+ */
+public class GitblitWicketFilter extends WicketFilter {
+	
+	/**
+	 * Determines the last-modified date of the requested resource.
+	 * 
+	 * @param servletRequest
+	 * @return The last modified time stamp
+	 */
+	protected long getLastModified(final HttpServletRequest servletRequest)	{
+		final String pathInfo = getRelativePath(servletRequest);
+		if (Strings.isEmpty(pathInfo))
+			return -1;
+		long lastModified = super.getLastModified(servletRequest);
+		if (lastModified > -1) {
+			return lastModified;
+		}
+		
+		// try to match request against registered CacheControl pages
+		String [] paths = pathInfo.split("/");
+		
+		String page = paths[0];
+		String repo = "";
+		String commitId = "";
+		if (paths.length >= 2) {
+			repo = paths[1];
+		}
+		if (paths.length >= 3) {
+			commitId = paths[2];
+		}
+		
+		if (!StringUtils.isEmpty(servletRequest.getParameter("r"))) {
+			repo = servletRequest.getParameter("r");
+		}
+		if (!StringUtils.isEmpty(servletRequest.getParameter("h"))) {
+			commitId = servletRequest.getParameter("h");
+		}
+		
+		repo = repo.replace("%2f", "/").replace("%2F", "/").replace(GitBlit.getChar(Keys.web.forwardSlashCharacter, '/'), '/');
+
+		GitBlitWebApp app = (GitBlitWebApp) getWebApplication();
+		int expires = GitBlit.getInteger(Keys.web.pageCacheExpires, 0);
+		if (!StringUtils.isEmpty(page) && app.isCacheablePage(page) && expires > 0) {
+			// page can be cached by the browser
+			CacheControl cacheControl = app.getCacheControl(page);
+			Date bootDate = GitBlit.getBootDate();
+			switch (cacheControl.value()) {
+			case ACTIVITY:
+				// returns the last activity date of the server
+				Date activityDate = GitBlit.getLastActivityDate();
+				if (activityDate != null) {
+					return activityDate.after(bootDate) ? activityDate.getTime() : bootDate.getTime();
+				}
+				return bootDate.getTime();
+			case BOOT:
+				// return the boot date of the server
+				return bootDate.getTime();
+			case PROJECT:
+				// return the latest change date for the project OR the boot date
+				ProjectModel project = GitBlit.self().getProjectModel(StringUtils.getRootPath(repo));
+				if (project != null) {
+					return project.lastChange.after(bootDate) ? project.lastChange.getTime() : bootDate.getTime();
+				}
+				break;
+			case REPOSITORY:
+				// return the lastest change date for the repository OR the boot
+				// date, whichever is latest
+				RepositoryModel repository = GitBlit.self().getRepositoryModel(repo);
+				if (repository != null && repository.lastChange != null) {
+					return repository.lastChange.after(bootDate) ? repository.lastChange.getTime() : bootDate.getTime();
+				}
+				break;
+			case COMMIT:
+				// get the date of the specified commit
+				if (StringUtils.isEmpty(commitId)) {
+					// no commit id, use boot date
+					return bootDate.getTime();
+				} else {
+					// last modified date is the commit date 
+					Repository r = null;
+					try {
+						// return the timestamp of the associated commit
+						r = GitBlit.self().getRepository(repo);
+						if (r != null) {
+							RevCommit commit = JGitUtils.getCommit(r, commitId);
+							if (commit != null) {
+								Date date = JGitUtils.getCommitDate(commit);
+								return date.after(bootDate) ? date.getTime() : bootDate.getTime();
+							}
+						}
+					} finally {
+						if (r != null) {
+							r.close();
+						}
+					}
+				}
+				break;
+			default:
+				break;
+			}
+		}			
+
+		return -1;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/ActivityPage.java b/src/main/java/com/gitblit/wicket/pages/ActivityPage.java
index 694fa60..413403b 100644
--- a/src/main/java/com/gitblit/wicket/pages/ActivityPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ActivityPage.java
@@ -35,7 +35,9 @@
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.utils.ActivityUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.PageRegistration;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
 import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
 import com.gitblit.wicket.WicketUtils;
@@ -52,6 +54,8 @@
  * @author James Moger
  * 
  */
+
+@CacheControl(LastModified.ACTIVITY)
 public class ActivityPage extends RootPage {
 
 	public ActivityPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.java b/src/main/java/com/gitblit/wicket/pages/BasePage.java
index 71713cd..c9e11b0 100644
--- a/src/main/java/com/gitblit/wicket/pages/BasePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BasePage.java
@@ -40,7 +40,10 @@
 import org.apache.wicket.markup.html.link.ExternalLink;
 import org.apache.wicket.markup.html.panel.FeedbackPanel;
 import org.apache.wicket.protocol.http.RequestUtils;
+import org.apache.wicket.protocol.http.WebResponse;
 import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
+import org.apache.wicket.util.time.Duration;
+import org.apache.wicket.util.time.Time;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -56,6 +59,7 @@
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TimeUtils;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.GitBlitWebApp;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.WicketUtils;
@@ -121,7 +125,68 @@
 			Application.get().getMarkupSettings().setStripWicketTags(false);
 		}
 		super.onAfterRender();
-	}	
+	}
+		
+	@Override
+	protected void setHeaders(WebResponse response)	{
+		int expires = GitBlit.getInteger(Keys.web.pageCacheExpires, 0);
+		if (expires > 0) {
+			// pages are personalized for the authenticated user so they must be
+			// marked private to prohibit proxy servers from caching them
+			response.setHeader("Cache-Control", "private, must-revalidate");
+			setLastModified();
+		} else {
+			// use default Wicket caching behavior
+			super.setHeaders(response);
+		}
+	}
+	
+	/**
+	 * Sets the last-modified header date, if appropriate, for this page.  The
+	 * date used is determined by the CacheControl annotation.
+	 * 
+	 */
+	protected void setLastModified() {
+		if (getClass().isAnnotationPresent(CacheControl.class)) {
+			CacheControl cacheControl = getClass().getAnnotation(CacheControl.class);
+			switch (cacheControl.value()) {
+			case ACTIVITY:
+				setLastModified(GitBlit.getLastActivityDate());
+				break;
+			case BOOT:
+				setLastModified(GitBlit.getBootDate());
+				break;
+			case NONE:
+				break;
+			default:
+				logger.warn(getClass().getSimpleName() + ": unhandled LastModified type " + cacheControl.value());
+				break;
+			}
+		}
+	}
+	
+	/**
+	 * Sets the last-modified header field and the expires field.
+	 * 
+	 * @param when
+	 */
+	protected final void setLastModified(Date when) {
+		if (when == null) {
+			return;
+		}
+		
+		if (when.before(GitBlit.getBootDate())) {
+			// last-modified can not be before the Gitblit boot date
+			// this helps ensure that pages are properly refreshed after a
+			// server config change
+			when = GitBlit.getBootDate();
+		}
+		
+		int expires = GitBlit.getInteger(Keys.web.pageCacheExpires, 0);
+		WebResponse response = (WebResponse) getResponse();
+		response.setLastModifiedTime(Time.valueOf(when));
+		response.setDateHeader("Expires", System.currentTimeMillis() + Duration.minutes(expires).getMilliseconds());
+	}
 
 	protected void setupPage(String repositoryName, String pageName) {
 		String siteName = GitBlit.getString(Keys.web.siteName, Constants.NAME);
diff --git a/src/main/java/com/gitblit/wicket/pages/BlamePage.java b/src/main/java/com/gitblit/wicket/pages/BlamePage.java
index 5148915..53bd233 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlamePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BlamePage.java
@@ -37,11 +37,14 @@
 import com.gitblit.utils.DiffUtils;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.CommitHeaderPanel;
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
 
+@CacheControl(LastModified.BOOT)
 public class BlamePage extends RepositoryPage {
 
 	public BlamePage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
index 03edaa3..c297bca 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
@@ -27,10 +27,13 @@
 import com.gitblit.utils.DiffUtils.DiffOutputType;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.CommitHeaderPanel;
 import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
 
+@CacheControl(LastModified.BOOT)
 public class BlobDiffPage extends RepositoryPage {
 
 	public BlobDiffPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobPage.java b/src/main/java/com/gitblit/wicket/pages/BlobPage.java
index f6e9998..b104df2 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlobPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BlobPage.java
@@ -32,11 +32,14 @@
 import com.gitblit.Keys;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.ExternalImage;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.panels.CommitHeaderPanel;
 import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
 
+@CacheControl(LastModified.BOOT)
 public class BlobPage extends RepositoryPage {
 
 	protected String fileExtension;
diff --git a/src/main/java/com/gitblit/wicket/pages/BranchesPage.java b/src/main/java/com/gitblit/wicket/pages/BranchesPage.java
index 8684fb3..fe7483e 100644
--- a/src/main/java/com/gitblit/wicket/pages/BranchesPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BranchesPage.java
@@ -17,8 +17,11 @@
 
 import org.apache.wicket.PageParameters;
 
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.panels.BranchesPanel;
 
+@CacheControl(LastModified.REPOSITORY)
 public class BranchesPage extends RepositoryPage {
 
 	public BranchesPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
index 6954808..6f1b459 100644
--- a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
@@ -16,7 +16,6 @@
 package com.gitblit.wicket.pages;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 import org.apache.wicket.PageParameters;
@@ -30,22 +29,21 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 
-import com.gitblit.Constants;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.models.PathModel.PathChangeModel;
-import com.gitblit.models.GitNote;
 import com.gitblit.models.SubmoduleModel;
 import com.gitblit.utils.DiffUtils;
 import com.gitblit.utils.DiffUtils.DiffOutputType;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.CommitHeaderPanel;
 import com.gitblit.wicket.panels.CommitLegendPanel;
-import com.gitblit.wicket.panels.GravatarImage;
 import com.gitblit.wicket.panels.LinkPanel;
-import com.gitblit.wicket.panels.RefsPanel;
 
+@CacheControl(LastModified.BOOT)
 public class CommitDiffPage extends RepositoryPage {
 
 	public CommitDiffPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitPage.java b/src/main/java/com/gitblit/wicket/pages/CommitPage.java
index b98dba5..1d11d44 100644
--- a/src/main/java/com/gitblit/wicket/pages/CommitPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/CommitPage.java
@@ -37,6 +37,8 @@
 import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.models.SubmoduleModel;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.CommitHeaderPanel;
 import com.gitblit.wicket.panels.CommitLegendPanel;
@@ -45,6 +47,7 @@
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.RefsPanel;
 
+@CacheControl(LastModified.BOOT)
 public class CommitPage extends RepositoryPage {
 
 	public CommitPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/DocsPage.java b/src/main/java/com/gitblit/wicket/pages/DocsPage.java
index 9ddc98d..9330316 100644
--- a/src/main/java/com/gitblit/wicket/pages/DocsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/DocsPage.java
@@ -30,9 +30,12 @@
 import com.gitblit.models.PathModel;
 import com.gitblit.utils.ByteFormat;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.panels.LinkPanel;
 
+@CacheControl(LastModified.REPOSITORY)
 public class DocsPage extends RepositoryPage {
 
 	public DocsPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/GitSearchPage.java b/src/main/java/com/gitblit/wicket/pages/GitSearchPage.java
index 154bdcc..446531a 100644
--- a/src/main/java/com/gitblit/wicket/pages/GitSearchPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/GitSearchPage.java
@@ -19,9 +19,12 @@
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 
 import com.gitblit.Constants;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.panels.SearchPanel;
 
+@CacheControl(LastModified.REPOSITORY)
 public class GitSearchPage extends RepositoryPage {
 
 	public GitSearchPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/HistoryPage.java b/src/main/java/com/gitblit/wicket/pages/HistoryPage.java
index 314bdef..33bc54c 100644
--- a/src/main/java/com/gitblit/wicket/pages/HistoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/HistoryPage.java
@@ -18,9 +18,12 @@
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.panels.HistoryPanel;
 
+@CacheControl(LastModified.REPOSITORY)
 public class HistoryPage extends RepositoryPage {
 
 	public HistoryPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/LogPage.java b/src/main/java/com/gitblit/wicket/pages/LogPage.java
index ee8ddfe..1f4a9bf 100644
--- a/src/main/java/com/gitblit/wicket/pages/LogPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/LogPage.java
@@ -19,9 +19,12 @@
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.panels.LogPanel;
 
+@CacheControl(LastModified.REPOSITORY)
 public class LogPage extends RepositoryPage {
 
 	public LogPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java b/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java
index 7f82b64..df078c7 100644
--- a/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java
@@ -29,8 +29,11 @@
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
 
+@CacheControl(LastModified.BOOT)
 public class MarkdownPage extends RepositoryPage {
 
 	public MarkdownPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/MetricsPage.java b/src/main/java/com/gitblit/wicket/pages/MetricsPage.java
index 3195020..3aa1fcc 100644
--- a/src/main/java/com/gitblit/wicket/pages/MetricsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/MetricsPage.java
@@ -40,9 +40,12 @@
 import com.gitblit.models.Metric;
 import com.gitblit.utils.MetricUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.charting.SecureChart;
 
+@CacheControl(LastModified.REPOSITORY)
 public class MetricsPage extends RepositoryPage {
 
 	public MetricsPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java
index d951da3..32c128d 100644
--- a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java
@@ -43,11 +43,14 @@
 import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.panels.FilterableProjectList;
 import com.gitblit.wicket.panels.FilterableRepositoryList;
 
+@CacheControl(LastModified.ACTIVITY)
 public class MyDashboardPage extends DashboardPage {
 
 	public MyDashboardPage() {
diff --git a/src/main/java/com/gitblit/wicket/pages/OverviewPage.java b/src/main/java/com/gitblit/wicket/pages/OverviewPage.java
index ff6326d..e1de9f3 100644
--- a/src/main/java/com/gitblit/wicket/pages/OverviewPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/OverviewPage.java
@@ -34,8 +34,10 @@
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.charting.GoogleChart;
 import com.gitblit.wicket.charting.GoogleCharts;
 import com.gitblit.wicket.charting.GoogleLineChart;
@@ -45,6 +47,7 @@
 import com.gitblit.wicket.panels.RepositoryUrlPanel;
 import com.gitblit.wicket.panels.TagsPanel;
 
+@CacheControl(LastModified.REPOSITORY)
 public class OverviewPage extends RepositoryPage {
 
 	public OverviewPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/PatchPage.java b/src/main/java/com/gitblit/wicket/pages/PatchPage.java
index 878cfb4..be959d0 100644
--- a/src/main/java/com/gitblit/wicket/pages/PatchPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/PatchPage.java
@@ -25,9 +25,12 @@
 import com.gitblit.utils.DiffUtils;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.WicketUtils;
 
+@CacheControl(LastModified.BOOT)
 public class PatchPage extends WebPage {
 
 	public PatchPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectPage.java b/src/main/java/com/gitblit/wicket/pages/ProjectPage.java
index 97a31f9..c938891 100644
--- a/src/main/java/com/gitblit/wicket/pages/ProjectPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectPage.java
@@ -33,6 +33,8 @@
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.GitBlitWebApp;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.GitblitRedirectException;
@@ -42,6 +44,7 @@
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.FilterableRepositoryList;
 
+@CacheControl(LastModified.PROJECT)
 public class ProjectPage extends DashboardPage {
 	
 	List<ProjectModel> projectModels = new ArrayList<ProjectModel>();
@@ -60,6 +63,26 @@
 		return RepositoriesPage.class;
 	}
 
+	@Override
+	protected void setLastModified() {
+		if (getClass().isAnnotationPresent(CacheControl.class)) {
+			CacheControl cacheControl = getClass().getAnnotation(CacheControl.class);
+			switch (cacheControl.value()) {
+			case PROJECT:
+				String projectName = WicketUtils.getProjectName(getPageParameters());
+				if (!StringUtils.isEmpty(projectName)) {
+					ProjectModel project = getProjectModel(projectName);
+					if (project != null) {
+						setLastModified(project.lastChange);
+					}
+				}
+				break;
+			default:
+				super.setLastModified();
+			}
+		}
+	}
+	
 	private void setup(PageParameters params) {
 		setupPage("", "");
 		// check to see if we should display a login message
diff --git a/src/main/java/com/gitblit/wicket/pages/ReflogPage.java b/src/main/java/com/gitblit/wicket/pages/ReflogPage.java
index 884f616..c97b2cc 100644
--- a/src/main/java/com/gitblit/wicket/pages/ReflogPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ReflogPage.java
@@ -18,9 +18,12 @@
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.panels.ReflogPanel;
 
+@CacheControl(LastModified.REPOSITORY)
 public class ReflogPage extends RepositoryPage {
 
 	public ReflogPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java
index 7d4d68c..a4a1ab5 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java
@@ -33,13 +33,16 @@
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.PageRegistration;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
 import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.RepositoriesPanel;
 
+@CacheControl(LastModified.ACTIVITY)
 public class RepositoriesPage extends RootPage {
 
 	public RepositoriesPage() {
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
index 372b68d..f5b8c96 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -19,6 +19,7 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -63,6 +64,7 @@
 import com.gitblit.utils.RefLogUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TicgitUtils;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.PageRegistration;
 import com.gitblit.wicket.PageRegistration.OtherPageLink;
@@ -572,6 +574,30 @@
 		setupPage(repositoryName, "/ " + getPageName());
 		super.onBeforeRender();
 	}
+	
+	@Override
+	protected void setLastModified() {
+		if (getClass().isAnnotationPresent(CacheControl.class)) {
+			CacheControl cacheControl = getClass().getAnnotation(CacheControl.class);
+			switch (cacheControl.value()) {
+			case REPOSITORY:
+				RepositoryModel repository = getRepositoryModel();
+				if (repository != null) {
+					setLastModified(repository.lastChange);
+				}
+				break;
+			case COMMIT:
+				RevCommit commit = getCommit();
+				if (commit != null) {
+					Date commitDate = JGitUtils.getCommitDate(commit);
+					setLastModified(commitDate);
+				}
+				break;
+			default:
+				super.setLastModified();
+			}
+		}
+	}
 
 	protected PageParameters newRepositoryParameter() {
 		return WicketUtils.newRepositoryParameter(repositoryName);
diff --git a/src/main/java/com/gitblit/wicket/pages/SummaryPage.java b/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
index c231b2b..17c41eb 100644
--- a/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
@@ -50,8 +50,10 @@
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.charting.SecureChart;
 import com.gitblit.wicket.panels.BranchesPanel;
 import com.gitblit.wicket.panels.LinkPanel;
@@ -59,6 +61,7 @@
 import com.gitblit.wicket.panels.RepositoryUrlPanel;
 import com.gitblit.wicket.panels.TagsPanel;
 
+@CacheControl(LastModified.REPOSITORY)
 public class SummaryPage extends RepositoryPage {
 
 	public SummaryPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/TagPage.java b/src/main/java/com/gitblit/wicket/pages/TagPage.java
index 6deef78..13c1011 100644
--- a/src/main/java/com/gitblit/wicket/pages/TagPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TagPage.java
@@ -28,11 +28,14 @@
 
 import com.gitblit.models.RefModel;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.GravatarImage;
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.RefsPanel;
 
+@CacheControl(LastModified.BOOT)
 public class TagPage extends RepositoryPage {
 
 	public TagPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/TagsPage.java b/src/main/java/com/gitblit/wicket/pages/TagsPage.java
index b052531..2abb410 100644
--- a/src/main/java/com/gitblit/wicket/pages/TagsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TagsPage.java
@@ -17,8 +17,11 @@
 
 import org.apache.wicket.PageParameters;
 
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.panels.TagsPanel;
 
+@CacheControl(LastModified.REPOSITORY)
 public class TagsPage extends RepositoryPage {
 
 	public TagsPage(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/pages/TreePage.java b/src/main/java/com/gitblit/wicket/pages/TreePage.java
index bc27f0c..25bcd67 100644
--- a/src/main/java/com/gitblit/wicket/pages/TreePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TreePage.java
@@ -33,12 +33,15 @@
 import com.gitblit.models.SubmoduleModel;
 import com.gitblit.utils.ByteFormat;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.CommitHeaderPanel;
 import com.gitblit.wicket.panels.CompressedDownloadsPanel;
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
 
+@CacheControl(LastModified.BOOT)
 public class TreePage extends RepositoryPage {
 
 	public TreePage(PageParameters params) {

--
Gitblit v1.9.1