Benki: Cache HTML renderings of posts in the database.

Change-Id: I3367ceb8769d354f64165d23ec7ed1f2155c4c49
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java
index bbfafa2..aa15fa2 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java
@@ -47,6 +47,8 @@
 
   private static final Logger log = Logger.getLogger(Post.class);
 
+  private static final int DESCRIPTION_CACHE_VERSION = 1;
+
   @Id
   @SequenceGenerator(
       allocationSize = 1,
@@ -61,6 +63,14 @@
   @CheckForNull
   public OffsetDateTime date;
 
+  @Column(name = "cached_description_version", nullable = true)
+  @CheckForNull
+  public Integer cachedDescriptionVersion;
+
+  @Column(name = "cached_description_html", nullable = true)
+  @CheckForNull
+  public String cachedDescriptionHtml;
+
   @ManyToOne(fetch = FetchType.LAZY)
   @JoinColumn(name = "owner", referencedColumnName = "id")
   @CheckForNull
@@ -93,7 +103,21 @@
   public abstract String getTitle();
 
   @CheckForNull
-  public abstract String getDescriptionHtml();
+  public final String getDescriptionHtml() {
+    if (cachedDescriptionHtml != null &&
+        cachedDescriptionVersion != null &&
+        cachedDescriptionVersion >= DESCRIPTION_CACHE_VERSION){
+      return cachedDescriptionHtml;
+    } else {
+      @CheckForNull var descriptionHtml = computeDescriptionHtml();
+      cachedDescriptionHtml = descriptionHtml;
+      cachedDescriptionVersion = DESCRIPTION_CACHE_VERSION;
+      return descriptionHtml;
+    }
+  }
+
+  @CheckForNull
+  protected abstract String computeDescriptionHtml();
 
   @CheckForNull
   public abstract String getUri();
@@ -190,6 +214,10 @@
       this.posts = resultList;
     }
 
+    public void cacheDescriptions() {
+      days().forEach(Day::cacheDescriptions);
+    }
+
     public class Day {
       public final @CheckForNull LocalDate date;
       public final List<T> posts;
@@ -198,6 +226,12 @@
         this.date = date;
         this.posts = posts;
       }
+
+      public void cacheDescriptions() {
+        for (var post : posts) {
+          post.getDescriptionHtml();
+        }
+      }
     }
 
     public List<Day> days() {
@@ -211,9 +245,9 @@
     }
   }
 
-  public static List<Post> findViewable(
+  public static PostPage<Post> findViewable(
       PostFilter postFilter, Session session, @CheckForNull User viewer, @CheckForNull User owner) {
-    return findViewable(postFilter, session, viewer, owner, null, null).posts;
+    return findViewable(postFilter, session, viewer, owner, null, null);
   }
 
   public static PostPage<Post> findViewable(