@Override protected CompletableFuture<Latest<T>> doWatch(CentralDogma client, String projectName, String repositoryName, Revision lastKnownRevision, long timeoutMillis) { return client.watchFile(projectName, repositoryName, lastKnownRevision, query, timeoutMillis) .thenApply(result -> { if (result == null) { return null; } return new Latest<>(result.revision(), function.apply(result.content())); }); } }
private CompletableFuture<?> watchFile(Repository repository, Revision lastKnownRevision, Query<?> query, long timeOutMillis) { final CompletableFuture<? extends Entry<?>> future = watchService.watchFile( repository, lastKnownRevision, query, timeOutMillis); return future.thenApply(entry -> { final Revision revision = entry.revision(); final EntryDto<?> entryDto = convert(repository, entry); return (Object) new WatchResultDto(revision, entryDto); }).exceptionally(ContentServiceV1::handleWatchFailure); }
private CompletableFuture<?> watchFile(Repository repository, Revision lastKnownRevision, Query<?> query, long timeOutMillis) { final CompletableFuture<? extends Entry<?>> future = watchService.watchFile( repository, lastKnownRevision, query, timeOutMillis); return future.thenApply(entry -> { final Revision revision = entry.revision(); final EntryDto<?> entryDto = convert(repository, entry); return (Object) new WatchResultDto(revision, entryDto); }).exceptionally(ContentServiceV1::handleWatchFailure); }
@Override protected CompletableFuture<Latest<T>> doWatch(CentralDogma client, String projectName, String repositoryName, Revision lastKnownRevision, long timeoutMillis) { return client.watchFile(projectName, repositoryName, lastKnownRevision, query, timeoutMillis) .thenApply(result -> { if (result == null) { return null; } return new Latest<>(result.revision(), function.apply(result.content())); }); } }
@Override protected CompletableFuture<Latest<T>> doWatch(CentralDogma client, String projectName, String repositoryName, Revision lastKnownRevision, long timeoutMillis) { return client.watchFile(projectName, repositoryName, lastKnownRevision, query, timeoutMillis) .thenApply(result -> { if (result == null) { return null; } return new Latest<>(result.revision(), function.apply(result.content())); }); } }
private static CompletableFuture<Object> handleWatchFileSuccess( Repository repository, Query<?> query, Entry<?> entry) { final Revision revision = entry.revision(); final EntryDto<?> entryDto = convert(repository, entry); return repository.history(revision, revision, query.path()) .thenApply(commits -> { // The size of 'commits' should be 1. return convert(commits.get(0), entryDto); }); }
@Test(timeout = 10000) public void testWatchWithIdentityQuery() throws Exception { final Revision rev1 = repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, textUpserts[0]).join(); final CompletableFuture<Entry<String>> f = repo.watch(rev1, Query.ofText(textPaths[0])); final Revision rev2 = repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, textPatches[1]).join(); final Entry<String> res = f.get(3, TimeUnit.SECONDS); assertThat(res.revision()).isEqualTo(rev2); assertThat(res.type()).isEqualTo(EntryType.TEXT); // Text must be sanitized so that the last line ends with \n. assertThat(res.content()).isEqualTo(textUpserts[1].content() + '\n'); }
/** * Applies the specified {@link Query} to the {@link Entry#content()} of the specified {@link Entry} and * returns the query result. * * @throws IllegalStateException if the specified {@link Entry} is a directory * @throws QuerySyntaxException if the syntax of specified {@link Query} is invalid * @throws QueryExecutionException if an {@link Exception} is raised while applying the specified * {@link Query} to the {@link Entry#content()} */ static <T> Entry<T> applyQuery(Entry<T> entry, Query<T> query) { requireNonNull(query, "query"); entry.content(); // Ensure that content is not null. final EntryType entryType = entry.type(); final QueryType queryType = query.type(); if (!queryType.supportedEntryTypes().contains(entryType)) { throw new QueryExecutionException("Unsupported entry type: " + entryType + " (query: " + query + ')'); } if (queryType == IDENTITY) { return entry; } else if (queryType == JSON_PATH) { return Entry.of(entry.revision(), query.path(), entryType, query.apply(entry.content())); } else { throw new QueryExecutionException("Unsupported entry type: " + entryType + " (query: " + query + ')'); } }
/** * Applies the specified {@link Query} to the {@link Entry#content()} of the specified {@link Entry} and * returns the query result. * * @throws IllegalStateException if the specified {@link Entry} is a directory * @throws QuerySyntaxException if the syntax of specified {@link Query} is invalid * @throws QueryExecutionException if an {@link Exception} is raised while applying the specified * {@link Query} to the {@link Entry#content()} */ static <T> Entry<T> applyQuery(Entry<T> entry, Query<T> query) { requireNonNull(query, "query"); entry.content(); // Ensure that content is not null. final EntryType entryType = entry.type(); final QueryType queryType = query.type(); if (!queryType.supportedEntryTypes().contains(entryType)) { throw new QueryExecutionException("Unsupported entry type: " + entryType + " (query: " + query + ')'); } if (queryType == IDENTITY) { return entry; } else if (queryType == JSON_PATH) { return Entry.of(entry.revision(), query.path(), entryType, query.apply(entry.content())); } else { throw new QueryExecutionException("Unsupported entry type: " + entryType + " (query: " + query + ')'); } }
@Test public void testGetFiles() throws Exception { final Revision headRev = rule.client().normalizeRevision( rule.project(), rule.repo1(), Revision.HEAD).join(); final Map<String, Entry<?>> files = rule.client().getFiles( rule.project(), rule.repo1(), Revision.HEAD, TEST_ROOT + "*.json").join(); assertThat(files).hasSize(NUM_FILES); files.values().forEach(f -> { assertThat(f.revision()).isEqualTo(headRev); assertThatJson(f.content()).isEqualTo("{}"); }); }
@Test public void testWatchWithQuery() throws Exception { final Revision rev1 = repo.commit( HEAD, 0L, Author.UNKNOWN, SUMMARY, Change.ofJsonUpsert(jsonPaths[0], "{ \"hello\": \"mars\" }")).join(); final CompletableFuture<Entry<JsonNode>> f = repo.watch(rev1, Query.ofJsonPath(jsonPaths[0], "$.hello")); // Make sure the initial change does not trigger a notification. assertThatThrownBy(() -> f.get(500, TimeUnit.MILLISECONDS)) .isInstanceOf(TimeoutException.class); // Make sure the change that does not affect the query result does not trigger a notification. repo.commit( HEAD, 0L, Author.UNKNOWN, SUMMARY, Change.ofJsonUpsert(jsonPaths[0], "{ \"hello\": \"mars\", \"goodbye\": \"venus\" }")); assertThatThrownBy(() -> f.get(500, TimeUnit.MILLISECONDS)) .isInstanceOf(TimeoutException.class); // Here comes the interesting change; make sure notification is triggered. final Revision rev3 = repo.commit( HEAD, 0L, Author.UNKNOWN, SUMMARY, Change.ofJsonUpsert(jsonPaths[0], "{ \"hello\": \"jupiter\", \"goodbye\": \"mars\" }")).join(); final Entry<JsonNode> res = f.get(3, TimeUnit.SECONDS); assertThat(res.revision()).isEqualTo(rev3); assertThat(res.type()).isEqualTo(EntryType.JSON); assertThat(res.content()).isEqualTo(TextNode.valueOf("jupiter")); }
@Test public void testWatchRemoval() throws Exception { final String path = jsonPaths[0]; final Change<JsonNode> upsert1 = Change.ofJsonUpsert(path, "1"); final Change<JsonNode> upsert2 = Change.ofJsonUpsert(path, "2"); final Revision rev1 = repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, upsert1).join(); final CompletableFuture<Entry<JsonNode>> f = repo.watch(rev1, Query.ofJson(path)); // Remove the file being watched. repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, Change.ofRemoval(path)).join(); // Should wait patiently until the file reappears. assertThatThrownBy(() -> f.get(1, TimeUnit.SECONDS)).isInstanceOf(TimeoutException.class); // Add the file back again without changing the content. repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, upsert1).join(); // Should wait patiently until the file changes really. assertThatThrownBy(() -> f.get(1, TimeUnit.SECONDS)).isInstanceOf(TimeoutException.class); // Remove the file being watched again. repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, Change.ofRemoval(path)).join(); // Should wait patiently until the file reappears. assertThatThrownBy(() -> f.get(1, TimeUnit.SECONDS)).isInstanceOf(TimeoutException.class); // Add the file back again with different content. final Revision rev2 = repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, upsert2).join(); // Should be notified this time because the content has changed really. final Entry<JsonNode> res = f.get(3, TimeUnit.SECONDS); assertThat(res.revision()).isEqualTo(rev2); assertThat(res.type()).isEqualTo(EntryType.JSON); assertThatJson(res.content()).isEqualTo(upsert2.content()); }
private static void handleWatchFileResult( CompletableFuture<com.linecorp.centraldogma.common.Entry<Object>> future, AsyncMethodCallback resultHandler) { future.handle((res, cause) -> { if (cause == null) { final WatchFileResult wfr = new WatchFileResult(); wfr.setRevision(convert(res.revision())); wfr.setType(convert(res.type())); wfr.setContent(res.contentAsText()); resultHandler.onComplete(wfr); } else if (cause instanceof CancellationException) { resultHandler.onComplete(CentralDogmaConstants.EMPTY_WATCH_FILE_RESULT); } else { logAndInvokeOnError("watchFile", resultHandler, cause); } return null; }); }
static EntryDto convert(Entry<?> entry) { final EntryDto dto = new EntryDto(); dto.setRevision(entry.revision().text()); dto.setPath(entry.path()); dto.setType(entry.type().name()); dto.setContent(entry.contentAsText()); return dto; }
private static void handleWatchFileResult( CompletableFuture<com.linecorp.centraldogma.common.Entry<Object>> future, AsyncMethodCallback resultHandler) { future.handle((res, cause) -> { if (cause == null) { final WatchFileResult wfr = new WatchFileResult(); wfr.setRevision(convert(res.revision())); wfr.setType(convert(res.type())); wfr.setContent(res.contentAsText()); resultHandler.onComplete(wfr); } else if (cause instanceof CancellationException) { resultHandler.onComplete(CentralDogmaConstants.EMPTY_WATCH_FILE_RESULT); } else { logAndInvokeOnError("watchFile", resultHandler, cause); } return null; }); }
static EntryDto convert(Entry<?> entry) { final EntryDto dto = new EntryDto(); dto.setRevision(entry.revision().text()); dto.setPath(entry.path()); dto.setType(entry.type().name()); dto.setContent(entry.contentAsText()); return dto; }
static EntryDto convert(Entry<?> entry) { final EntryDto dto = new EntryDto(); dto.setRevision(entry.revision().text()); dto.setPath(entry.path()); dto.setType(entry.type().name()); dto.setContent(entry.contentAsText()); return dto; }
private static void handleWatchFileResult( CompletableFuture<com.linecorp.centraldogma.common.Entry<Object>> future, AsyncMethodCallback resultHandler) { future.handle(voidFunction((res, cause) -> { if (cause == null) { final WatchFileResult wfr = new WatchFileResult(); wfr.setRevision(convert(res.revision())); wfr.setType(convert(res.type())); wfr.setContent(res.contentAsText()); resultHandler.onComplete(wfr); } else if (cause instanceof CancellationException) { resultHandler.onComplete(CentralDogmaConstants.EMPTY_WATCH_FILE_RESULT); } else { logAndInvokeOnError("watchFile", resultHandler, cause); } })); }
@Test public void ofDirectory() throws Exception { final Entry<Void> e = Entry.ofDirectory(new Revision(1), "/"); assertThat(e.revision()).isEqualTo(new Revision(1)); assertThat(e.hasContent()).isFalse(); e.ifHasContent(unused -> fail()); assertThatThrownBy(e::content).isInstanceOf(IllegalStateException.class); assertThatThrownBy(e::contentAsJson).isInstanceOf(IllegalStateException.class); assertThatThrownBy(e::contentAsText).isInstanceOf(IllegalStateException.class); assertThatThrownBy(e::contentAsPrettyText).isInstanceOf(IllegalStateException.class); assertThatThrownBy(() -> e.contentAsJson(JsonNode.class)).isInstanceOf(IllegalStateException.class); // directory vs. directory final Entry<Void> e2 = Entry.ofDirectory(new Revision(1), "/"); assertThat(e).isEqualTo(e2); assertThat(e.hashCode()).isEqualTo(e2.hashCode()); assertThat(e).isNotEqualTo(Entry.ofDirectory(new Revision(2), "/")); assertThat(e).isNotEqualTo(Entry.ofDirectory(new Revision(1), "/foo")); // directory vs. text file final Entry<String> e3 = Entry.ofText(new Revision(1), "/a.txt", "foo"); assertThat(e).isNotEqualTo(e3); assertThat(e.hashCode()).isNotEqualTo(e3.hashCode()); // directory vs. JSON file final Entry<JsonNode> e4 = Entry.ofJson(new Revision(1), "/a.json", "{ \"foo\": \"bar\" }"); assertThat(e).isNotEqualTo(e4); assertThat(e.hashCode()).isNotEqualTo(e4.hashCode()); }
@Test public void ofJson() throws Exception { final Entry<JsonNode> e = Entry.ofJson(new Revision(1), "/a.json", "{ \"foo\": \"bar\" }"); assertThat(e.revision()).isEqualTo(new Revision(1)); assertThat(e.hasContent()).isTrue(); e.ifHasContent(content -> assertThatJson(content).isEqualTo("{ \"foo\": \"bar\" }")); assertThatJson(e.content()).isEqualTo("{ \"foo\": \"bar\" }"); assertThat(e.contentAsText()).isEqualTo("{\"foo\":\"bar\"}"); assertThat(e.contentAsPrettyText()).isEqualTo("{\n \"foo\": \"bar\"\n}"); assertThat(e.content()).isSameAs(e.contentAsJson()); assertThat(e.content()).isEqualTo(e.contentAsJson(JsonNode.class)); // JSON file vs. JSON file final Entry<JsonNode> e2 = Entry.ofJson(new Revision(1), "/a.json", "{ \"foo\": \"bar\" }"); assertThat(e).isEqualTo(e2); assertThat(e.hashCode()).isEqualTo(e2.hashCode()); assertThat(e).isNotEqualTo(Entry.ofJson(new Revision(2), "/a.json", "{ \"foo\": \"bar\" }")); assertThat(e).isNotEqualTo(Entry.ofJson(new Revision(1), "/b.json", "{ \"foo\": \"bar\" }")); assertThat(e).isNotEqualTo(Entry.ofJson(new Revision(1), "/a.json", "null")); // JSON file vs. text file final Entry<String> e3 = Entry.ofText(new Revision(1), "/a.json", "{\"foo\":\"bar\"}"); assertThat(e).isNotEqualTo(e3); assertThat(e.hashCode()).isNotEqualTo(e3.hashCode()); // JSON file vs. directory final Entry<Void> e4 = Entry.ofDirectory(new Revision(1), "/foo"); assertThat(e).isNotEqualTo(e4); assertThat(e.hashCode()).isNotEqualTo(e4.hashCode()); }