Reload documents if meta data of any parent project is changed

Meta data changes in a parent project may affect the rendering of
documents. Hence documents should be reloaded if there was a change in
the refs/meta/config branch of any parent project. To achieve this a
hash computed out of the revision IDs of the refs/meta/config branches
of all parent project is included into the document cache key. As
result there is no cache hit when the meta data of a parent project
was changed and the document is reloaded.

This will also enable the implementation of inheritable CSS.

Change-Id: I9dabffabc8a003ef42a21e280eeba355462c2a8a
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocCache.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocCache.java
index 013a1c5..3d1226f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocCache.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocCache.java
@@ -15,6 +15,8 @@
 package com.googlesource.gerrit.plugins.xdocs;
 
 import com.google.common.cache.LoadingCache;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
 import com.google.gerrit.httpd.resources.Resource;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.project.ProjectCache;
@@ -48,6 +50,21 @@
             ? p.getConfig().getRevision()
             : ObjectId.zeroId();
     return cache.getUnchecked((new XDocResourceKey(formatter.getName(),
-        project, file, revId, metaConfigRevId)).asString());
+        project, file, revId, metaConfigRevId, getParentsHash(project)))
+        .asString());
+  }
+
+  private String getParentsHash(Project.NameKey project) {
+    Hasher h = Hashing.md5().newHasher();
+    ProjectState p = projectCache.get(project);
+    if (p != null) {
+      for (ProjectState parent : p.parents()) {
+        h.putUnencodedChars(
+            parent.getConfig().getRevision() != null
+                ? parent.getConfig().getRevision().getName()
+                : ObjectId.zeroId().getName());
+      }
+    }
+    return h.hash().toString();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocResourceKey.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocResourceKey.java
index 5bae456..178eea2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocResourceKey.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocResourceKey.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.xdocs;
 
+import com.google.common.base.Strings;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.reviewdb.client.Project;
 
@@ -27,14 +28,16 @@
   private final String resource;
   private final ObjectId revId;
   private final ObjectId metaConfigRevId;
+  private final String parentsHash;
 
   XDocResourceKey(String formatter, Project.NameKey project, String r,
-      ObjectId revId, ObjectId metaConfigRevId) {
+      ObjectId revId, ObjectId metaConfigRevId, String parentsHash) {
     this.formatter = formatter;
     this.project = project;
     this.resource = r;
     this.revId = revId;
     this.metaConfigRevId = metaConfigRevId;
+    this.parentsHash = parentsHash;
   }
 
   public String getFormatter() {
@@ -55,7 +58,8 @@
 
   @Override
   public int hashCode() {
-    return Objects.hash(formatter, project, resource, revId, metaConfigRevId);
+    return Objects.hash(formatter, project, resource, revId, metaConfigRevId,
+        parentsHash);
   }
 
   @Override
@@ -66,7 +70,8 @@
           && Objects.equals(project, rk.project)
           && Objects.equals(resource, rk.resource)
           && Objects.equals(revId, rk.revId)
-          && Objects.equals(metaConfigRevId, rk.metaConfigRevId);
+          && Objects.equals(metaConfigRevId, rk.metaConfigRevId)
+          && Objects.equals(parentsHash, rk.parentsHash);
     }
     return false;
   }
@@ -82,6 +87,8 @@
     b.append(revId != null ? revId.name() : "");
     b.append("/");
     b.append(metaConfigRevId != null ? metaConfigRevId.name() : "");
+    b.append("/");
+    b.append(Strings.nullToEmpty(parentsHash));
     return b.toString();
   }
 
@@ -92,6 +99,7 @@
     String file = null;
     String revision = null;
     String metaConfigRevision = null;
+    String parentsHash = null;
     if (s.length > 0) {
       formatter = IdString.fromUrl(s[0]).get();
     }
@@ -107,8 +115,11 @@
     if (s.length > 4) {
       metaConfigRevision = s[4];
     }
+    if (s.length > 5) {
+      parentsHash = s[5];
+    }
     return new XDocResourceKey(formatter, new Project.NameKey(project), file,
-        toObjectId(revision), toObjectId(metaConfigRevision));
+        toObjectId(revision), toObjectId(metaConfigRevision), parentsHash);
   }
 
   private static ObjectId toObjectId(String id) {