blob: 654db5fb71a2faa3c65b30fa71ef5527492d9f43 [file] [log] [blame]
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +02001package eu.mulk.mulkcms2.benki.posts;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +01002
Matthias Andreas Benkard2d4f92e2020-02-09 16:15:07 +01003import eu.mulk.mulkcms2.benki.accesscontrol.Role;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +01004import eu.mulk.mulkcms2.benki.bookmarks.Bookmark;
5import eu.mulk.mulkcms2.benki.lazychat.LazychatMessage;
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +01006import eu.mulk.mulkcms2.benki.users.User;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +01007import eu.mulk.mulkcms2.benki.users.User_;
Matthias Andreas Benkard35cb1592020-01-24 11:05:20 +01008import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +01009import io.quarkus.security.identity.SecurityIdentity;
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010010import java.time.OffsetDateTime;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +010011import java.util.ArrayList;
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +010012import java.util.List;
13import java.util.Objects;
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010014import java.util.Set;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +010015import javax.annotation.CheckForNull;
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +020016import javax.json.bind.annotation.JsonbTransient;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010017import javax.persistence.Column;
18import javax.persistence.Entity;
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010019import javax.persistence.FetchType;
Matthias Andreas Benkard0246c3e2020-01-27 05:39:08 +010020import javax.persistence.GeneratedValue;
21import javax.persistence.GenerationType;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010022import javax.persistence.Id;
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010023import javax.persistence.Inheritance;
24import javax.persistence.InheritanceType;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010025import javax.persistence.JoinColumn;
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010026import javax.persistence.JoinTable;
27import javax.persistence.ManyToMany;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010028import javax.persistence.ManyToOne;
Matthias Andreas Benkard0246c3e2020-01-27 05:39:08 +010029import javax.persistence.SequenceGenerator;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010030import javax.persistence.Table;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +010031import javax.persistence.criteria.CriteriaBuilder;
32import javax.persistence.criteria.CriteriaQuery;
33import javax.persistence.criteria.From;
34import javax.persistence.criteria.JoinType;
35import javax.persistence.criteria.Predicate;
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +010036import org.hibernate.Session;
37import org.jboss.logging.Logger;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010038
39@Entity
Matthias Andreas Benkard57c9a8a2020-01-24 19:09:38 +010040@Table(name = "posts", schema = "benki")
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010041@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
42public abstract class Post extends PanacheEntityBase {
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010043
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +010044 private static Logger log = Logger.getLogger(Post.class);
45
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010046 @Id
Matthias Andreas Benkard0246c3e2020-01-27 05:39:08 +010047 @SequenceGenerator(
48 allocationSize = 1,
49 sequenceName = "posts_id_seq",
50 name = "posts_id_seq",
51 schema = "benki")
52 @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "posts_id_seq")
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010053 @Column(name = "id", nullable = false)
Matthias Andreas Benkard0246c3e2020-01-27 05:39:08 +010054 public Integer id;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010055
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010056 @Column(name = "date", nullable = true)
Matthias Andreas Benkardd9b95882020-01-24 11:42:49 +010057 public OffsetDateTime date;
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010058
Matthias Andreas Benkardaa754802020-01-24 11:55:26 +010059 @ManyToOne(fetch = FetchType.LAZY)
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +010060 @JoinColumn(name = "owner", referencedColumnName = "id")
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +020061 @JsonbTransient
Matthias Andreas Benkard35cb1592020-01-24 11:05:20 +010062 public User owner;
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010063
64 @ManyToMany(fetch = FetchType.LAZY)
65 @JoinTable(
66 name = "user_visible_posts",
Matthias Andreas Benkard553de3e2020-01-27 05:33:15 +010067 schema = "benki",
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010068 joinColumns = @JoinColumn(name = "message"),
69 inverseJoinColumns = @JoinColumn(name = "user"))
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +020070 @JsonbTransient
Matthias Andreas Benkardf9c74272020-01-24 11:51:35 +010071 public Set<User> visibleTo;
Matthias Andreas Benkard2d4f92e2020-02-09 16:15:07 +010072
73 @ManyToMany(fetch = FetchType.LAZY)
74 @JoinTable(
75 name = "post_targets",
76 schema = "benki",
77 joinColumns = @JoinColumn(name = "message"),
78 inverseJoinColumns = @JoinColumn(name = "target"))
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +020079 @JsonbTransient
Matthias Andreas Benkard2d4f92e2020-02-09 16:15:07 +010080 public Set<Role> targets;
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +010081
Matthias Andreas Benkard371164a2020-03-23 06:21:25 +010082 public abstract boolean isBookmark();
83
84 public abstract boolean isLazychatMessage();
85
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +020086 @CheckForNull
87 public abstract String getTitle();
88
89 @CheckForNull
90 public abstract String getDescriptionHtml();
91
92 @CheckForNull
93 public abstract String getUri();
94
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +020095 public Visibility getVisibility() {
96 if (targets.isEmpty()) {
97 return Visibility.PRIVATE;
98 } else if (targets.contains(Role.getWorld())) {
99 return Visibility.PUBLIC;
100 } else {
101 // FIXME: There should really be a check whether targets.equals(owner.defaultTargets) here.
102 // Otherwise the actual visibility is DISCRETIONARY.
103 return Visibility.SEMIPRIVATE;
104 }
105 }
106
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100107 protected static <T extends Post> CriteriaQuery<T> queryViewable(
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100108 Class<T> entityClass,
109 SecurityIdentity readerIdentity,
110 @CheckForNull User owner,
111 @CheckForNull Integer cursor,
112 CriteriaBuilder cb,
113 boolean forward) {
114 CriteriaQuery<T> query = cb.createQuery(entityClass);
115
116 var conditions = new ArrayList<Predicate>();
117
118 From<?, T> post;
119 if (readerIdentity.isAnonymous()) {
120 post = query.from(entityClass);
121 var target = post.join(Post_.targets);
122 conditions.add(cb.equal(target, Role.getWorld()));
123 } else {
124 var userName = readerIdentity.getPrincipal().getName();
125 var user = User.findByNickname(userName);
126
127 var root = query.from(User.class);
128 conditions.add(cb.equal(root, user));
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200129 if (entityClass.isAssignableFrom(Post.class)) {
130 post = (From<?, T>) root.join(User_.visiblePosts);
131 } else if (entityClass.isAssignableFrom(Bookmark.class)) {
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100132 post = (From<?, T>) root.join(User_.visibleBookmarks);
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200133 } else if (entityClass.isAssignableFrom(LazychatMessage.class)) {
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100134 post = (From<?, T>) root.join(User_.visibleLazychatMessages);
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200135 } else {
Matthias Andreas Benkardca4d7942020-04-18 14:13:41 +0200136 throw new IllegalArgumentException();
Matthias Andreas Benkardf5999552020-03-22 06:52:06 +0100137 }
138 }
139
140 query.select(post);
141 post.fetch(Post_.owner, JoinType.LEFT);
142
143 if (owner != null) {
144 conditions.add(cb.equal(post.get(Post_.owner), owner));
145 }
146
147 if (forward) {
148 query.orderBy(cb.desc(post.get(Post_.id)));
149 } else {
150 query.orderBy(cb.asc(post.get(Post_.id)));
151 }
152
153 if (cursor != null) {
154 if (forward) {
155 conditions.add(cb.le(post.get(Post_.id), cursor));
156 } else {
157 conditions.add(cb.gt(post.get(Post_.id), cursor));
158 }
159 }
160
161 query.where(conditions.toArray(new Predicate[0]));
162
163 return query;
164 }
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100165
166 public static class PostPage<T extends Post> {
167 public @CheckForNull Integer prevCursor;
168 public @CheckForNull Integer cursor;
169 public @CheckForNull Integer nextCursor;
170 public List<T> posts;
171
172 private PostPage(
173 @CheckForNull Integer c0,
174 @CheckForNull Integer c1,
175 @CheckForNull Integer c2,
176 List<T> resultList) {
177 this.prevCursor = c0;
178 this.cursor = c1;
179 this.nextCursor = c2;
180 this.posts = resultList;
181 }
182 }
183
Matthias Andreas Benkardd5ae0d52020-03-29 18:57:22 +0200184 public static List<Post> findViewable(
185 PostFilter postFilter, Session session, SecurityIdentity viewer, @CheckForNull User owner) {
186 return findViewable(postFilter, session, viewer, owner, null, null).posts;
187 }
188
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200189 public static PostPage<Post> findViewable(
190 PostFilter postFilter,
191 Session session,
192 SecurityIdentity viewer,
193 @CheckForNull User owner,
194 @CheckForNull Integer cursor,
195 @CheckForNull Integer count) {
196 Class<? extends Post> entityClass;
197 switch (postFilter) {
198 case BOOKMARKS_ONLY:
199 entityClass = Bookmark.class;
200 break;
201 case LAZYCHAT_MESSAGES_ONLY:
202 entityClass = LazychatMessage.class;
203 break;
204 default:
205 entityClass = Post.class;
206 }
207 return findViewable(entityClass, session, viewer, owner, cursor, count);
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100208 }
209
210 protected static <T extends Post> PostPage<T> findViewable(
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200211 Class<? extends T> entityClass,
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100212 Session session,
213 SecurityIdentity viewer,
214 @CheckForNull User owner,
215 @CheckForNull Integer cursor,
216 @CheckForNull Integer count) {
217
218 if (cursor != null) {
219 Objects.requireNonNull(count);
220 }
221
222 var cb = session.getCriteriaBuilder();
223
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200224 var forwardCriteria = queryViewable(entityClass, viewer, owner, cursor, cb, true);
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100225 var forwardQuery = session.createQuery(forwardCriteria);
226
227 if (count != null) {
228 forwardQuery.setMaxResults(count + 1);
229 }
230
231 log.debug(forwardQuery.unwrap(org.hibernate.query.Query.class).getQueryString());
232
233 @CheckForNull Integer prevCursor = null;
234 @CheckForNull Integer nextCursor = null;
235
236 if (cursor != null) {
237 // Look backwards as well so we can find the prevCursor.
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200238 var backwardCriteria = queryViewable(entityClass, viewer, owner, cursor, cb, false);
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100239 var backwardQuery = session.createQuery(backwardCriteria);
240 backwardQuery.setMaxResults(count);
241 var backwardResults = backwardQuery.getResultList();
242 if (!backwardResults.isEmpty()) {
243 prevCursor = backwardResults.get(backwardResults.size() - 1).id;
244 }
245 }
246
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200247 var forwardResults = (List<T>) forwardQuery.getResultList();
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100248 if (count != null) {
249 if (forwardResults.size() == count + 1) {
250 nextCursor = forwardResults.get(count).id;
251 forwardResults.remove((int) count);
252 }
253 }
254
Matthias Andreas Benkard4940b292020-03-29 18:41:07 +0200255 return new PostPage<T>(prevCursor, cursor, nextCursor, forwardResults);
Matthias Andreas Benkard3d399f32020-03-22 07:23:07 +0100256 }
Matthias Andreas Benkard06e6c812020-04-13 17:01:35 +0200257
258 public enum Visibility {
259 PUBLIC,
260 SEMIPRIVATE,
261 DISCRETIONARY,
262 PRIVATE,
263 }
264
265 @Override
266 public boolean equals(Object o) {
267 if (this == o) {
268 return true;
269 }
270 if (!(o instanceof Post)) {
271 return false;
272 }
273 Post post = (Post) o;
274 return Objects.equals(id, post.id);
275 }
276
277 @Override
278 public int hashCode() {
279 return Objects.hash(id);
280 }
Matthias Andreas Benkard734879e2020-01-24 10:47:37 +0100281}