diff options
Diffstat (limited to 'src/main/java')
11 files changed, 390 insertions, 81 deletions
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/Bookmark.java b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/Bookmark.java index a659049..256c988 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/Bookmark.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/Bookmark.java @@ -1,7 +1,6 @@ package eu.mulk.mulkcms2.benki.bookmarks; import eu.mulk.mulkcms2.benki.posts.Post; -import eu.mulk.mulkcms2.common.markdown.MarkdownConverter; import java.util.Set; import javax.annotation.CheckForNull; import javax.persistence.CollectionTable; @@ -11,23 +10,14 @@ import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.Table; -import javax.persistence.Transient; @Entity @Table(name = "bookmarks", schema = "benki") -public class Bookmark extends Post { +public class Bookmark extends Post<BookmarkText> { @Column(name = "uri", nullable = false, length = -1) public String uri; - @Column(name = "title", nullable = true, length = -1) - @CheckForNull - public String title; - - @Column(name = "description", nullable = true, length = -1) - @CheckForNull - public String description; - @ElementCollection(fetch = FetchType.LAZY) @CollectionTable( name = "bookmark_tags", @@ -36,13 +26,10 @@ public class Bookmark extends Post { @Column(name = "tag") public Set<String> tags; - @Transient @CheckForNull - protected String computeDescriptionHtml() { - if (description == null) { - return null; - } - return new MarkdownConverter().htmlify(description); + private String getDescription() { + var text = getText(); + return text == null ? null : text.description; } @CheckForNull @@ -54,7 +41,8 @@ public class Bookmark extends Post { @CheckForNull @Override public String getTitle() { - return title; + var text = getText(); + return text == null ? null : text.title; } @Override @@ -66,4 +54,29 @@ public class Bookmark extends Post { public boolean isLazychatMessage() { return false; } + + public void setTitle(String x) { + var text = getText(); + if (text == null) { + text = new BookmarkText(); + text.post = this; + text.language = ""; + texts.put(text.language, text); + } + + text.title = x; + } + + public void setDescription(String x) { + var text = getText(); + if (text == null) { + text = new BookmarkText(); + text.post = this; + text.language = ""; + texts.put(text.language, text); + } + + text.description = x; + text.cachedDescriptionHtml = null; + } } diff --git a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java index f81ed04..bb39be9 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java @@ -66,9 +66,9 @@ public class BookmarkResource extends PostResource { var bookmark = new Bookmark(); bookmark.uri = uri.toString(); - bookmark.title = title; bookmark.tags = Set.of(); - bookmark.description = description; + bookmark.setTitle(title); + bookmark.setDescription(description); bookmark.owner = user; bookmark.date = OffsetDateTime.now(); @@ -106,10 +106,9 @@ public class BookmarkResource extends PostResource { } bookmark.uri = uri.toString(); - bookmark.title = title; bookmark.tags = Set.of(); - bookmark.description = description; - bookmark.cachedDescriptionHtml = null; + bookmark.setTitle(title); + bookmark.setDescription(description); bookmark.owner = user; assignPostTargets(visibility, user, bookmark); diff --git a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkText.java b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkText.java new file mode 100644 index 0000000..c30f3df --- /dev/null +++ b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkText.java @@ -0,0 +1,31 @@ +package eu.mulk.mulkcms2.benki.bookmarks; + +import eu.mulk.mulkcms2.benki.posts.PostText; +import eu.mulk.mulkcms2.common.markdown.MarkdownConverter; +import javax.annotation.CheckForNull; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.Transient; + +@Entity +@Table(name = "bookmark_texts", schema = "benki") +public class BookmarkText extends PostText<Bookmark> { + + @Column(name = "title", nullable = true, length = -1) + @CheckForNull + public String title; + + @Column(name = "description", nullable = true, length = -1) + @CheckForNull + public String description; + + @Transient + @CheckForNull + protected String computeDescriptionHtml() { + if (description == null) { + return null; + } + return new MarkdownConverter().htmlify(description); + } +} diff --git a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkTextPK.java b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkTextPK.java new file mode 100644 index 0000000..92bda99 --- /dev/null +++ b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkTextPK.java @@ -0,0 +1,54 @@ +package eu.mulk.mulkcms2.benki.bookmarks; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Column; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +public class BookmarkTextPK implements Serializable { + + @Id + @Column(name = "language", nullable = false, length = -1) + private String language; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "bookmark", referencedColumnName = "id", nullable = false) + private Bookmark bookmark; + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public Bookmark getBookmark() { + return bookmark; + } + + public void setBookmark(Bookmark bookmark) { + this.bookmark = bookmark; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BookmarkTextPK)) { + return false; + } + BookmarkTextPK that = (BookmarkTextPK) o; + return Objects.equals(getBookmark(), that.getBookmark()) + && getLanguage().equals(that.getLanguage()); + } + + @Override + public int hashCode() { + return Objects.hash(getBookmark(), getLanguage()); + } +} diff --git a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java index 5cec6aa..aa5b6eb 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java @@ -1,11 +1,9 @@ package eu.mulk.mulkcms2.benki.lazychat; import eu.mulk.mulkcms2.benki.posts.Post; -import eu.mulk.mulkcms2.common.markdown.MarkdownConverter; import java.util.Collection; import javax.annotation.CheckForNull; import javax.json.bind.annotation.JsonbTransient; -import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.OneToMany; @@ -14,14 +12,7 @@ import javax.persistence.Transient; @Entity @Table(name = "lazychat_messages", schema = "benki") -public class LazychatMessage extends Post { - - @Column(name = "content", nullable = true, length = -1) - @CheckForNull - public String content; - - @Column(name = "format", nullable = false, length = -1) - public String format; +public class LazychatMessage extends Post<LazychatMessageText> { @OneToMany(mappedBy = "referrer", fetch = FetchType.LAZY) @JsonbTransient @@ -46,16 +37,6 @@ public class LazychatMessage extends Post { return null; } - @CheckForNull - @Override - @JsonbTransient - protected String computeDescriptionHtml() { - if (content == null) { - return null; - } - return new MarkdownConverter().htmlify(content); - } - @Override public boolean isBookmark() { return false; @@ -65,4 +46,17 @@ public class LazychatMessage extends Post { public boolean isLazychatMessage() { return true; } + + public void setContent(String x) { + var text = getText(); + if (text == null) { + text = new LazychatMessageText(); + text.post = this; + text.language = ""; + texts.put(text.language, text); + } + + text.cachedDescriptionHtml = null; + text.content = x; + } } diff --git a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageText.java b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageText.java new file mode 100644 index 0000000..1a60877 --- /dev/null +++ b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageText.java @@ -0,0 +1,28 @@ +package eu.mulk.mulkcms2.benki.lazychat; + +import eu.mulk.mulkcms2.benki.posts.PostText; +import eu.mulk.mulkcms2.common.markdown.MarkdownConverter; +import javax.annotation.CheckForNull; +import javax.json.bind.annotation.JsonbTransient; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity +@Table(name = "lazychat_message_texts", schema = "benki") +public class LazychatMessageText extends PostText<LazychatMessage> { + + @Column(name = "content", nullable = true, length = -1) + @CheckForNull + public String content; + + @CheckForNull + @Override + @JsonbTransient + protected String computeDescriptionHtml() { + if (content == null) { + return null; + } + return new MarkdownConverter().htmlify(content); + } +} diff --git a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageTextPK.java b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageTextPK.java new file mode 100644 index 0000000..33063b1 --- /dev/null +++ b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageTextPK.java @@ -0,0 +1,54 @@ +package eu.mulk.mulkcms2.benki.lazychat; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Column; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +public class LazychatMessageTextPK implements Serializable { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "lazychat_message", referencedColumnName = "id", nullable = false) + public LazychatMessage lazychatMessage; + + @Id + @Column(name = "language", nullable = false, length = -1) + private String language; + + public LazychatMessage getLazychatMessage() { + return lazychatMessage; + } + + public void setLazychatMessageId(LazychatMessage lazychatMessage) { + this.lazychatMessage = lazychatMessage; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof LazychatMessageTextPK)) { + return false; + } + LazychatMessageTextPK that = (LazychatMessageTextPK) o; + return Objects.equals(getLazychatMessage(), that.getLazychatMessage()) + && getLanguage().equals(that.getLanguage()); + } + + @Override + public int hashCode() { + return Objects.hash(getLazychatMessage(), getLanguage()); + } +} diff --git a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatResource.java b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatResource.java index 156b638..270a3d0 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatResource.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatResource.java @@ -45,8 +45,7 @@ public class LazychatResource extends PostResource { var user = Objects.requireNonNull(getCurrentUser()); var message = new LazychatMessage(); - message.content = text; - message.format = "markdown"; + message.setContent(text); message.owner = user; message.date = OffsetDateTime.now(); @@ -81,9 +80,7 @@ public class LazychatResource extends PostResource { throw new ForbiddenException(); } - message.content = text; - message.cachedDescriptionHtml = null; - message.format = "markdown"; + message.setContent(text); assignPostTargets(visibility, user, message); 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 2bd9ade..8f2166c 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java @@ -1,5 +1,7 @@ package eu.mulk.mulkcms2.benki.posts; +import static java.util.stream.Collectors.toList; + import eu.mulk.mulkcms2.benki.accesscontrol.Role; import eu.mulk.mulkcms2.benki.bookmarks.Bookmark; import eu.mulk.mulkcms2.benki.lazychat.LazychatMessage; @@ -10,7 +12,9 @@ import java.time.LocalDate; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TimeZone; @@ -18,6 +22,7 @@ import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import javax.json.bind.annotation.JsonbTransient; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -30,6 +35,8 @@ import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; +import javax.persistence.MapKey; +import javax.persistence.OneToMany; import javax.persistence.SequenceGenerator; import javax.persistence.Table; import javax.persistence.criteria.CriteriaBuilder; @@ -43,12 +50,10 @@ import org.jboss.logging.Logger; @Entity @Table(name = "posts", schema = "benki") @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) -public abstract class Post extends PanacheEntityBase { +public abstract class Post<Text extends PostText<?>> extends PanacheEntityBase { private static final Logger log = Logger.getLogger(Post.class); - private static final int DESCRIPTION_CACHE_VERSION = 1; - @Id @SequenceGenerator( allocationSize = 1, @@ -63,14 +68,6 @@ public abstract class Post extends PanacheEntityBase { @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 @@ -95,6 +92,18 @@ public abstract class Post extends PanacheEntityBase { @JsonbTransient public Set<Role> targets; + @OneToMany( + mappedBy = "post", + fetch = FetchType.LAZY, + cascade = CascadeType.ALL, + targetEntity = PostText.class) + @MapKey(name = "language") + public Map<String, Text> texts = new HashMap<>(); + + public Map<String, Text> getTexts() { + return texts; + } + public abstract boolean isBookmark(); public abstract boolean isLazychatMessage(); @@ -103,23 +112,6 @@ public abstract class Post extends PanacheEntityBase { public abstract String getTitle(); @CheckForNull - 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(); public Visibility getVisibility() { @@ -195,7 +187,16 @@ public abstract class Post extends PanacheEntityBase { return getVisibility() == Visibility.PUBLIC || (user != null && visibleTo.contains(user)); } - public static class PostPage<T extends Post> { + @CheckForNull + public final String getDescriptionHtml() { + var text = getText(); + if (text == null) { + return null; + } + return text.getDescriptionHtml(); + } + + public static class PostPage<T extends Post<? extends PostText>> { public @CheckForNull final Integer prevCursor; public @CheckForNull final Integer cursor; public @CheckForNull final Integer nextCursor; @@ -229,7 +230,7 @@ public abstract class Post extends PanacheEntityBase { public void cacheDescriptions() { for (var post : posts) { - post.getDescriptionHtml(); + post.getTexts().values().forEach(PostText::getDescriptionHtml); } } } @@ -245,12 +246,12 @@ public abstract class Post extends PanacheEntityBase { } } - public static PostPage<Post> findViewable( + public static PostPage<Post<? extends PostText>> findViewable( PostFilter postFilter, Session session, @CheckForNull User viewer, @CheckForNull User owner) { return findViewable(postFilter, session, viewer, owner, null, null); } - public static PostPage<Post> findViewable( + public static PostPage<Post<? extends PostText>> findViewable( PostFilter postFilter, Session session, @CheckForNull User viewer, @@ -271,7 +272,7 @@ public abstract class Post extends PanacheEntityBase { return findViewable(entityClass, session, viewer, owner, cursor, count); } - protected static <T extends Post> PostPage<T> findViewable( + protected static <T extends Post<? extends PostText>> PostPage<T> findViewable( Class<? extends T> entityClass, Session session, @CheckForNull User viewer, @@ -316,9 +317,31 @@ public abstract class Post extends PanacheEntityBase { } } + // Fetch texts (to avoid n+1 selects). + var postIds = forwardResults.stream().map(x -> x.id).collect(toList()); + + if (!postIds.isEmpty()) { + find("SELECT p FROM Post p LEFT JOIN FETCH p.texts WHERE p.id IN (?1)", postIds).stream() + .count(); + } + return new PostPage<>(prevCursor, cursor, nextCursor, forwardResults); } + @CheckForNull + protected Text getText() { + var texts = getTexts(); + if (texts.isEmpty()) { + return null; + } else if (texts.containsKey("")) { + return texts.get(""); + } else if (texts.containsKey("en")) { + return texts.get("en"); + } else { + return texts.values().stream().findAny().get(); + } + } + public enum Visibility { PUBLIC, SEMIPRIVATE, diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostText.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostText.java new file mode 100644 index 0000000..01753dc --- /dev/null +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostText.java @@ -0,0 +1,61 @@ +package eu.mulk.mulkcms2.benki.posts; + +import javax.annotation.CheckForNull; +import javax.json.bind.annotation.JsonbTransient; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "post_texts", schema = "benki") +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@IdClass(PostTextPK.class) +public abstract class PostText<OwningPost extends Post<?>> { + + private static final int DESCRIPTION_CACHE_VERSION = 1; + + @Id + @Column(name = "post", nullable = false, insertable = false, updatable = false) + public int postId; + + @Id + @Column(name = "language", nullable = false, length = -1) + public String language; + + @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, targetEntity = Post.class) + @JoinColumn(name = "post", referencedColumnName = "id", nullable = false) + @JsonbTransient + public OwningPost post; + + @CheckForNull + 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(); +} diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostTextPK.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostTextPK.java new file mode 100644 index 0000000..0a945dd --- /dev/null +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostTextPK.java @@ -0,0 +1,55 @@ +package eu.mulk.mulkcms2.benki.posts; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Column; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +@IdClass(PostTextPK.class) +public class PostTextPK implements Serializable { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post", referencedColumnName = "id", nullable = false) + public Post<?> post; + + @Id + @Column(name = "language", nullable = false, length = -1) + private String language; + + public Post<?> getPost() { + return post; + } + + public void setPost(Post post) { + this.post = post; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PostTextPK)) { + return false; + } + PostTextPK that = (PostTextPK) o; + return Objects.equals(getPost(), that.getPost()) && getLanguage().equals(that.getLanguage()); + } + + @Override + public int hashCode() { + return Objects.hash(getPost(), getLanguage()); + } +} |