/
TagPostCountUpdater.java
186 lines (161 loc) · 6.59 KB
/
TagPostCountUpdater.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package run.halo.app.content;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static run.halo.app.extension.index.query.QueryFactory.and;
import static run.halo.app.extension.index.query.QueryFactory.equal;
import static run.halo.app.extension.index.query.QueryFactory.isNull;
import com.google.common.collect.Sets;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import run.halo.app.core.extension.content.Post;
import run.halo.app.core.extension.content.Tag;
import run.halo.app.core.extension.content.Tag.TagStatus;
import run.halo.app.event.post.PostDeletedEvent;
import run.halo.app.event.post.PostEvent;
import run.halo.app.event.post.PostUpdatedEvent;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.ListOptions;
import run.halo.app.extension.MetadataUtil;
import run.halo.app.extension.PageRequestImpl;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.DefaultController;
import run.halo.app.extension.controller.DefaultQueue;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.extension.controller.RequestQueue;
import run.halo.app.extension.router.selector.FieldSelector;
import run.halo.app.extension.router.selector.LabelSelector;
import run.halo.app.infra.utils.JsonUtils;
/**
* Update {@link TagStatus#postCount} when post related to tag is updated.
*
* @author guqing
* @since 2.13.0
*/
@Component
public class TagPostCountUpdater
implements Reconciler<TagPostCountUpdater.PostRelatedTags>, SmartLifecycle {
private final RequestQueue<PostRelatedTags> tagQueue;
private final Controller postEventController;
private final ExtensionClient client;
private volatile boolean running = false;
/**
* Construct a {@link TagPostCountUpdater} with the given {@link ExtensionClient}.
*/
public TagPostCountUpdater(ExtensionClient client) {
this.client = client;
this.tagQueue = new DefaultQueue<>(Instant::now);
this.postEventController = this.setupWith(null);
}
@Override
public Result reconcile(PostRelatedTags postRelatedTags) {
for (var tag : postRelatedTags.tags()) {
updateTagRelatedPostCount(tag);
}
// Update last associated tags when handled
client.fetch(Post.class, postRelatedTags.postName()).ifPresent(post -> {
var tags = defaultIfNull(post.getSpec().getTags(), List.<String>of());
var annotations = MetadataUtil.nullSafeAnnotations(post);
var tagAnno = JsonUtils.objectToJson(tags);
var oldTagAnno = annotations.get(Post.LAST_ASSOCIATED_TAGS_ANNO);
if (!tagAnno.equals(oldTagAnno)) {
annotations.put(Post.LAST_ASSOCIATED_TAGS_ANNO, tagAnno);
client.update(post);
}
});
return Result.doNotRetry();
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return new DefaultController<>(
this.getClass().getName(),
this,
tagQueue,
null,
Duration.ofMillis(100),
Duration.ofMinutes(10)
);
}
@Override
public void start() {
postEventController.start();
running = true;
}
@Override
public void stop() {
running = false;
postEventController.dispose();
}
@Override
public boolean isRunning() {
return running;
}
/**
* Listen to post event to calculate post related to tag for updating.
*/
@EventListener(PostEvent.class)
public void onPostUpdated(PostEvent postEvent) {
var postName = postEvent.getName();
if (postEvent instanceof PostUpdatedEvent) {
var tagsToUpdate = calcTagsToUpdate(postEvent.getName());
tagQueue.addImmediately(new PostRelatedTags(postName, tagsToUpdate));
return;
}
if (postEvent instanceof PostDeletedEvent deletedEvent) {
var tags = defaultIfNull(deletedEvent.getPost().getSpec().getTags(),
List.<String>of());
tagQueue.addImmediately(new PostRelatedTags(postName, Sets.newHashSet(tags)));
}
}
private Set<String> calcTagsToUpdate(String postName) {
var post = client.fetch(Post.class, postName).orElseThrow();
var annotations = MetadataUtil.nullSafeAnnotations(post);
var oldTags = Optional.ofNullable(annotations.get(Post.LAST_ASSOCIATED_TAGS_ANNO))
.filter(StringUtils::isNotBlank)
.map(tagsJson -> JsonUtils.jsonToObject(tagsJson, String[].class))
.orElse(new String[0]);
var tagsToUpdate = Sets.newHashSet(oldTags);
var newTags = post.getSpec().getTags();
if (newTags != null) {
tagsToUpdate.addAll(newTags);
}
return tagsToUpdate;
}
public record PostRelatedTags(String postName, Set<String> tags) {
}
private void updateTagRelatedPostCount(String tagName) {
client.fetch(Tag.class, tagName).ifPresent(tag -> {
var commonFieldQuery = and(
equal("spec.tags", tag.getMetadata().getName()),
isNull("metadata.deletionTimestamp")
);
// Update post count
var allPostOptions = new ListOptions();
allPostOptions.setFieldSelector(FieldSelector.of(commonFieldQuery));
var result = client.listBy(Post.class, allPostOptions, PageRequestImpl.ofSize(1));
tag.getStatusOrDefault().setPostCount((int) result.getTotal());
// Update visible post count
var publicPostOptions = new ListOptions();
publicPostOptions.setLabelSelector(LabelSelector.builder()
.eq(Post.PUBLISHED_LABEL, "true")
.build());
publicPostOptions.setFieldSelector(FieldSelector.of(
and(
commonFieldQuery,
equal("spec.deleted", "false"),
equal("spec.visible", Post.VisibleEnum.PUBLIC.name())
)
));
var publicPosts =
client.listBy(Post.class, publicPostOptions, PageRequestImpl.ofSize(1));
tag.getStatusOrDefault().setVisiblePostCount((int) publicPosts.getTotal());
client.update(tag);
});
}
}