1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.linecorp.centraldogma.server.storage.repository;
18
19 import static com.linecorp.centraldogma.internal.Util.unsafeCast;
20 import static com.linecorp.centraldogma.internal.Util.validateFilePath;
21 import static com.linecorp.centraldogma.internal.Util.validateJsonFilePath;
22 import static com.linecorp.centraldogma.server.storage.repository.FindOptions.FIND_ONE_WITHOUT_CONTENT;
23 import static com.linecorp.centraldogma.server.storage.repository.FindOptions.FIND_ONE_WITH_CONTENT;
24 import static com.linecorp.centraldogma.server.storage.repository.RepositoryUtil.applyQuery;
25 import static com.linecorp.centraldogma.server.storage.repository.RepositoryUtil.mergeEntries;
26 import static java.util.Objects.requireNonNull;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.concurrent.CompletableFuture;
33
34 import com.fasterxml.jackson.databind.JsonNode;
35 import com.google.common.collect.ImmutableList;
36 import com.google.common.collect.ImmutableMap;
37 import com.spotify.futures.CompletableFutures;
38
39 import com.linecorp.centraldogma.common.Author;
40 import com.linecorp.centraldogma.common.CentralDogmaException;
41 import com.linecorp.centraldogma.common.Change;
42 import com.linecorp.centraldogma.common.Commit;
43 import com.linecorp.centraldogma.common.Entry;
44 import com.linecorp.centraldogma.common.EntryNotFoundException;
45 import com.linecorp.centraldogma.common.EntryType;
46 import com.linecorp.centraldogma.common.Markup;
47 import com.linecorp.centraldogma.common.MergeQuery;
48 import com.linecorp.centraldogma.common.MergeSource;
49 import com.linecorp.centraldogma.common.MergedEntry;
50 import com.linecorp.centraldogma.common.Query;
51 import com.linecorp.centraldogma.common.QueryExecutionException;
52 import com.linecorp.centraldogma.common.Revision;
53 import com.linecorp.centraldogma.common.RevisionNotFoundException;
54 import com.linecorp.centraldogma.common.RevisionRange;
55 import com.linecorp.centraldogma.internal.HistoryConstants;
56 import com.linecorp.centraldogma.server.command.CommitResult;
57 import com.linecorp.centraldogma.server.command.ContentTransformer;
58 import com.linecorp.centraldogma.server.internal.replication.ReplicationLog;
59 import com.linecorp.centraldogma.server.storage.StorageException;
60 import com.linecorp.centraldogma.server.storage.project.Project;
61
62
63
64
65 public interface Repository {
66
67 int DEFAULT_MAX_COMMITS = 100;
68 int MAX_MAX_COMMITS = HistoryConstants.MAX_MAX_COMMITS;
69
70 String ALL_PATH = "/**";
71
72
73
74
75 org.eclipse.jgit.lib.Repository jGitRepository();
76
77
78
79
80 Project parent();
81
82
83
84
85 String name();
86
87
88
89
90 long creationTimeMillis();
91
92
93
94
95 Author author();
96
97
98
99
100
101
102
103 @Deprecated
104 default CompletableFuture<Revision> normalize(Revision revision) {
105 try {
106 return CompletableFuture.completedFuture(normalizeNow(revision));
107 } catch (Exception e) {
108 return CompletableFutures.exceptionallyCompletedFuture(e);
109 }
110 }
111
112
113
114
115
116
117 Revision normalizeNow(Revision revision);
118
119
120
121
122
123
124
125 RevisionRange normalizeNow(Revision from, Revision to);
126
127
128
129
130 default CompletableFuture<Boolean> exists(Revision revision, String path) {
131 validateFilePath(path, "path");
132 return find(revision, path, FIND_ONE_WITHOUT_CONTENT).thenApply(result -> !result.isEmpty());
133 }
134
135
136
137
138
139
140
141
142 default CompletableFuture<Entry<?>> get(Revision revision, String path) {
143 return getOrNull(revision, path).thenApply(entry -> {
144 if (entry == null) {
145 throw new EntryNotFoundException(revision, path);
146 }
147
148 return entry;
149 });
150 }
151
152
153
154
155
156
157
158
159 default <T> CompletableFuture<Entry<T>> get(Revision revision, Query<T> query) {
160 return getOrNull(revision, query).thenApply(res -> {
161 if (res == null) {
162 throw new EntryNotFoundException(revision, query.path());
163 }
164
165 return res;
166 });
167 }
168
169
170
171
172
173
174
175
176
177 default CompletableFuture<Entry<?>> getOrNull(Revision revision, String path) {
178 validateFilePath(path, "path");
179
180 return find(revision, path, FIND_ONE_WITH_CONTENT).thenApply(findResult -> findResult.get(path));
181 }
182
183
184
185
186
187
188
189
190
191 default <T> CompletableFuture<Entry<T>> getOrNull(Revision revision, Query<T> query) {
192 requireNonNull(query, "query");
193 requireNonNull(revision, "revision");
194
195 return getOrNull(revision, query.path()).thenApply(result -> {
196 if (result == null) {
197 return null;
198 }
199
200 @SuppressWarnings("unchecked")
201 final Entry<T> entry = (Entry<T>) result;
202
203 try {
204 return applyQuery(entry, query);
205 } catch (CentralDogmaException e) {
206 throw e;
207 } catch (Exception e) {
208 throw new QueryExecutionException(e);
209 }
210 });
211 }
212
213
214
215
216
217
218 default CompletableFuture<Map<String, Entry<?>>> find(Revision revision, String pathPattern) {
219 return find(revision, pathPattern, ImmutableMap.of());
220 }
221
222
223
224
225
226
227 CompletableFuture<Map<String, Entry<?>>> find(Revision revision, String pathPattern,
228 Map<FindOption<?>, ?> options);
229
230
231
232
233 default CompletableFuture<Change<?>> diff(Revision from, Revision to, Query<?> query) {
234 return diff(from, to, query, DiffResultType.NORMAL);
235 }
236
237
238
239
240 default CompletableFuture<Change<?>> diff(Revision from, Revision to, Query<?> query,
241 DiffResultType diffResultType) {
242 requireNonNull(from, "from");
243 requireNonNull(to, "to");
244 requireNonNull(query, "query");
245 requireNonNull(diffResultType, "diffResultType");
246
247 final RevisionRange range;
248 try {
249 range = normalizeNow(from, to).toAscending();
250 } catch (Exception e) {
251 return CompletableFutures.exceptionallyCompletedFuture(e);
252 }
253
254 final String path = query.path();
255 final CompletableFuture<Entry<?>> fromEntryFuture = getOrNull(range.from(), path);
256 final CompletableFuture<Entry<?>> toEntryFuture = getOrNull(range.to(), path);
257
258 final CompletableFuture<Change<?>> future =
259 CompletableFutures.combine(fromEntryFuture, toEntryFuture, (fromEntry, toEntry) -> {
260 @SuppressWarnings("unchecked")
261 final Query<Object> castQuery = (Query<Object>) query;
262
263
264 if (fromEntry != null) {
265 if (toEntry == null) {
266
267 return Change.ofRemoval(path);
268 }
269 } else if (toEntry != null) {
270
271 final EntryType toEntryType = toEntry.type();
272 if (!query.type().supportedEntryTypes().contains(toEntryType)) {
273 throw new QueryExecutionException("unsupported entry type: " + toEntryType);
274 }
275
276 final Object toContent = castQuery.apply(toEntry.content());
277
278 switch (toEntryType) {
279 case JSON:
280 return Change.ofJsonUpsert(path, (JsonNode) toContent);
281 case TEXT:
282 return Change.ofTextUpsert(path, (String) toContent);
283 default:
284 throw new Error();
285 }
286 } else {
287
288 throw new EntryNotFoundException(path + " (" + from + ", " + to + ')');
289 }
290
291
292 final EntryType entryType = fromEntry.type();
293 if (!query.type().supportedEntryTypes().contains(entryType)) {
294 throw new QueryExecutionException("unsupported entry type: " + entryType);
295 }
296 if (entryType != toEntry.type()) {
297 throw new QueryExecutionException(
298 "mismatching entry type: " + entryType + " != " + toEntry.type());
299 }
300
301 final Object fromContent = castQuery.apply(fromEntry.content());
302 final Object toContent = castQuery.apply(toEntry.content());
303
304 switch (entryType) {
305 case JSON:
306 if (diffResultType == DiffResultType.PATCH_TO_UPSERT) {
307 return Change.ofJsonUpsert(path, (JsonNode) toContent);
308 }
309 return Change.ofJsonPatch(path, (JsonNode) fromContent, (JsonNode) toContent);
310 case TEXT:
311 if (diffResultType == DiffResultType.PATCH_TO_UPSERT) {
312 return Change.ofTextUpsert(path, (String) toContent);
313 }
314 return Change.ofTextPatch(path, (String) fromContent, (String) toContent);
315 default:
316 throw new Error();
317 }
318 }).toCompletableFuture();
319 return unsafeCast(future);
320 }
321
322
323
324
325
326
327
328 default CompletableFuture<Map<String, Change<?>>> diff(Revision from, Revision to, String pathPattern) {
329 return diff(from, to, pathPattern, DiffResultType.NORMAL);
330 }
331
332
333
334
335
336
337
338 CompletableFuture<Map<String, Change<?>>> diff(Revision from, Revision to, String pathPattern,
339 DiffResultType diffResultType);
340
341
342
343
344 default CompletableFuture<Map<String, Change<?>>> previewDiff(Revision baseRevision, Change<?>... changes) {
345 requireNonNull(changes, "changes");
346 return previewDiff(baseRevision, Arrays.asList(changes));
347 }
348
349
350
351
352 CompletableFuture<Map<String, Change<?>>> previewDiff(Revision baseRevision, Iterable<Change<?>> changes);
353
354
355
356
357
358
359 default CompletableFuture<CommitResult> commit(Revision baseRevision, long commitTimeMillis,
360 Author author, String summary, Iterable<Change<?>> changes) {
361 return commit(baseRevision, commitTimeMillis, author, summary, "", Markup.PLAINTEXT, changes,
362 true);
363 }
364
365
366
367
368
369
370 default CompletableFuture<CommitResult> commit(Revision baseRevision, long commitTimeMillis,
371 Author author, String summary, Change<?>... changes) {
372 return commit(baseRevision, commitTimeMillis, author, summary, "", Markup.PLAINTEXT, changes);
373 }
374
375
376
377
378
379
380 default CompletableFuture<CommitResult> commit(Revision baseRevision, long commitTimeMillis,
381 Author author, String summary, String detail, Markup markup,
382 Change<?>... changes) {
383 requireNonNull(changes, "changes");
384 return commit(baseRevision, commitTimeMillis, author, summary, detail, markup,
385 ImmutableList.copyOf(changes), true);
386 }
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404 CompletableFuture<CommitResult> commit(Revision baseRevision, long commitTimeMillis,
405 Author author, String summary, String detail, Markup markup,
406 Iterable<Change<?>> changes, boolean directExecution);
407
408
409
410
411
412 CompletableFuture<CommitResult> commit(Revision baseRevision, long commitTimeMillis,
413 Author author, String summary, String detail, Markup markup,
414 ContentTransformer<?> transformer);
415
416
417
418
419
420
421
422
423
424
425
426
427 default CompletableFuture<List<Commit>> history(Revision from, Revision to, String pathPattern) {
428 return history(from, to, pathPattern, DEFAULT_MAX_COMMITS);
429 }
430
431
432
433
434
435
436
437
438
439
440
441
442
443 CompletableFuture<List<Commit>> history(Revision from, Revision to, String pathPattern, int maxCommits);
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464 default CompletableFuture<Revision> findLatestRevision(Revision lastKnownRevision, String pathPattern) {
465 return findLatestRevision(lastKnownRevision, pathPattern, false);
466 }
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487 CompletableFuture<Revision> findLatestRevision(Revision lastKnownRevision, String pathPattern,
488 boolean errorOnEntryNotFound);
489
490
491
492
493
494 default CompletableFuture<Revision> watch(Revision lastKnownRevision, String pathPattern) {
495 return watch(lastKnownRevision, pathPattern, false);
496 }
497
498
499
500
501
502 CompletableFuture<Revision> watch(Revision lastKnownRevision, String pathPattern,
503 boolean errorOnEntryNotFound);
504
505
506
507
508
509 default <T> CompletableFuture<Entry<T>> watch(Revision lastKnownRevision, Query<T> query) {
510 return watch(lastKnownRevision, query, false);
511 }
512
513
514
515
516
517 default <T> CompletableFuture<Entry<T>> watch(Revision lastKnownRevision, Query<T> query,
518 boolean errorOnEntryNotFound) {
519 return RepositoryUtil.watch(this, lastKnownRevision, query, errorOnEntryNotFound);
520 }
521
522
523
524
525 default <T> CompletableFuture<MergedEntry<T>> mergeFiles(Revision revision, MergeQuery<T> query) {
526 requireNonNull(revision, "revision");
527 requireNonNull(query, "query");
528
529 final List<MergeSource> mergeSources = query.mergeSources();
530
531 mergeSources.forEach(path -> validateJsonFilePath(path.path(), "path"));
532
533 final Revision normalizedRevision;
534 try {
535 normalizedRevision = normalizeNow(revision);
536 } catch (Exception e) {
537 return CompletableFutures.exceptionallyCompletedFuture(e);
538 }
539 final List<CompletableFuture<Entry<?>>> entryFutures = new ArrayList<>(mergeSources.size());
540 mergeSources.forEach(path -> {
541 if (!path.isOptional()) {
542 entryFutures.add(get(normalizedRevision, path.path()));
543 } else {
544 entryFutures.add(getOrNull(normalizedRevision, path.path()));
545 }
546 });
547
548 final CompletableFuture<MergedEntry<?>> mergedEntryFuture = mergeEntries(entryFutures, revision,
549 query);
550 final CompletableFuture<MergedEntry<T>> future = new CompletableFuture<>();
551 mergedEntryFuture.handle((mergedEntry, cause) -> {
552 if (cause != null) {
553 if (!(cause instanceof CentralDogmaException)) {
554 cause = new QueryExecutionException(cause);
555 }
556 future.completeExceptionally(cause);
557 return null;
558 }
559 future.complete(unsafeCast(mergedEntry));
560 return null;
561 });
562
563 return future;
564 }
565
566
567
568
569 <T> CompletableFuture<T> execute(CacheableCall<T> cacheableCall);
570
571
572
573
574
575 void addListener(RepositoryListener listener);
576 }