/
AbstractContentService.java
159 lines (146 loc) · 7.14 KB
/
AbstractContentService.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
package run.halo.app.content;
import java.security.Principal;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.lang.Nullable;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;
import run.halo.app.core.extension.content.Snapshot;
import run.halo.app.extension.MetadataUtil;
import run.halo.app.extension.ReactiveExtensionClient;
/**
* Abstract Service for {@link Snapshot}.
*
* @author guqing
* @since 2.0.0
*/
@Slf4j
@AllArgsConstructor
public abstract class AbstractContentService {
private final ReactiveExtensionClient client;
public Mono<ContentWrapper> getContent(String snapshotName, String baseSnapshotName) {
if (StringUtils.isBlank(snapshotName) || StringUtils.isBlank(baseSnapshotName)) {
return Mono.empty();
}
// TODO: refactor this method to use client.get instead of fetch but please be careful
return client.fetch(Snapshot.class, baseSnapshotName)
.doOnNext(this::checkBaseSnapshot)
.flatMap(baseSnapshot -> {
if (StringUtils.equals(snapshotName, baseSnapshotName)) {
var contentWrapper = ContentWrapper.patchSnapshot(baseSnapshot, baseSnapshot);
return Mono.just(contentWrapper);
}
return client.fetch(Snapshot.class, snapshotName)
.map(snapshot -> ContentWrapper.patchSnapshot(snapshot, baseSnapshot));
})
.switchIfEmpty(Mono.defer(() -> {
log.error("The content snapshot [{}] or base snapshot [{}] not found.",
snapshotName, baseSnapshotName);
return Mono.empty();
}));
}
protected void checkBaseSnapshot(Snapshot snapshot) {
Assert.notNull(snapshot, "The snapshot must not be null.");
if (!Snapshot.isBaseSnapshot(snapshot)) {
throw new IllegalArgumentException(
String.format("The snapshot [%s] is not a base snapshot.",
snapshot.getMetadata().getName()));
}
}
protected Mono<ContentWrapper> draftContent(@Nullable String baseSnapshotName,
ContentRequest contentRequest,
@Nullable String parentSnapshotName) {
Snapshot snapshot = contentRequest.toSnapshot();
snapshot.getMetadata().setName(UUID.randomUUID().toString());
snapshot.getSpec().setParentSnapshotName(parentSnapshotName);
final String baseSnapshotNameToUse =
StringUtils.defaultIfBlank(baseSnapshotName, snapshot.getMetadata().getName());
return client.fetch(Snapshot.class, baseSnapshotName)
.doOnNext(this::checkBaseSnapshot)
.defaultIfEmpty(snapshot)
.map(baseSnapshot -> determineRawAndContentPatch(snapshot, baseSnapshot,
contentRequest)
)
.flatMap(source -> getContextUsername()
.map(username -> {
Snapshot.addContributor(source, username);
source.getSpec().setOwner(username);
return source;
})
.defaultIfEmpty(source)
)
.flatMap(snapshotToCreate -> client.create(snapshotToCreate)
.flatMap(head -> restoredContent(baseSnapshotNameToUse, head)));
}
protected Mono<ContentWrapper> draftContent(String baseSnapshotName, ContentRequest content) {
return this.draftContent(baseSnapshotName, content, content.headSnapshotName());
}
protected Mono<ContentWrapper> updateContent(String baseSnapshotName,
ContentRequest contentRequest) {
Assert.notNull(contentRequest, "The contentRequest must not be null");
Assert.notNull(baseSnapshotName, "The baseSnapshotName must not be null");
Assert.notNull(contentRequest.headSnapshotName(), "The headSnapshotName must not be null");
return Mono.defer(() -> client.fetch(Snapshot.class, contentRequest.headSnapshotName())
.flatMap(headSnapshot -> client.fetch(Snapshot.class, baseSnapshotName)
.map(baseSnapshot -> determineRawAndContentPatch(headSnapshot, baseSnapshot,
contentRequest)
)
)
.flatMap(headSnapshot -> getContextUsername()
.map(username -> {
Snapshot.addContributor(headSnapshot, username);
return headSnapshot;
})
.defaultIfEmpty(headSnapshot)
)
.flatMap(client::update)
)
.retryWhen(Retry.backoff(5, Duration.ofMillis(100))
.filter(throwable -> throwable instanceof OptimisticLockingFailureException))
.flatMap(head -> restoredContent(baseSnapshotName, head));
}
protected Mono<ContentWrapper> restoredContent(String baseSnapshotName, Snapshot headSnapshot) {
return client.fetch(Snapshot.class, baseSnapshotName)
.doOnNext(this::checkBaseSnapshot)
.map(baseSnapshot -> ContentWrapper.patchSnapshot(headSnapshot, baseSnapshot));
}
protected Snapshot determineRawAndContentPatch(Snapshot snapshotToUse,
Snapshot baseSnapshot,
ContentRequest contentRequest) {
Assert.notNull(baseSnapshot, "The baseSnapshot must not be null.");
Assert.notNull(contentRequest, "The contentRequest must not be null.");
Assert.notNull(snapshotToUse, "The snapshotToUse not be null.");
String originalRaw = baseSnapshot.getSpec().getRawPatch();
String originalContent = baseSnapshot.getSpec().getContentPatch();
String baseSnapshotName = baseSnapshot.getMetadata().getName();
snapshotToUse.getSpec().setLastModifyTime(Instant.now());
// it is the v1 snapshot, set the content directly
if (StringUtils.equals(baseSnapshotName,
snapshotToUse.getMetadata().getName())) {
snapshotToUse.getSpec().setRawPatch(contentRequest.raw());
snapshotToUse.getSpec().setContentPatch(contentRequest.content());
MetadataUtil.nullSafeAnnotations(snapshotToUse)
.put(Snapshot.KEEP_RAW_ANNO, Boolean.TRUE.toString());
} else {
// otherwise diff a patch based on the v1 snapshot
String revisedRaw = contentRequest.rawPatchFrom(originalRaw);
String revisedContent = contentRequest.contentPatchFrom(originalContent);
snapshotToUse.getSpec().setRawPatch(revisedRaw);
snapshotToUse.getSpec().setContentPatch(revisedContent);
}
return snapshotToUse;
}
protected Mono<String> getContextUsername() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Principal::getName);
}
}