From f52b87acd0e83df2cd7fae494f28f9718d63eaf5 Mon Sep 17 00:00:00 2001 From: gautamomento <89037104+gautamomento@users.noreply.github.com> Date: Thu, 30 Sep 2021 10:25:49 -0700 Subject: [PATCH] feat: Add high-level wrappers for GetResponse (#46) * feat: Add high-level wrappers for GetResponse 1. Update the GetResponse Object to internally store ByteString. This allows our wrapper to use the utility methods on ByteString rather than having to author our own. 2. Added different entry points into set to allow customer options to set(String key, String value, ttl) set(String, ByteBuffer value, ttl) set(byte[] key, byte[] value, ttl) * update method names * update java doc * update exception construction --- .../intTest/java/momento/sdk/CacheTest.java | 12 ++-- .../intTest/java/momento/sdk/MomentoTest.java | 4 +- .../src/main/java/momento/sdk/Cache.java | 64 ++++++++++++------- .../momento/sdk/exceptions/SdkException.java | 1 - .../sdk/messages/ClientGetResponse.java | 25 ++++++-- 5 files changed, 68 insertions(+), 38 deletions(-) diff --git a/momento-sdk/src/intTest/java/momento/sdk/CacheTest.java b/momento-sdk/src/intTest/java/momento/sdk/CacheTest.java index 248370aa..48b2c61f 100644 --- a/momento-sdk/src/intTest/java/momento/sdk/CacheTest.java +++ b/momento-sdk/src/intTest/java/momento/sdk/CacheTest.java @@ -101,9 +101,9 @@ private static void testHappyPath(Cache cache) { assertEquals(MomentoCacheResult.Ok, setRsp.getResult()); // Get Key that was just set - ClientGetResponse rsp = cache.get(key); + ClientGetResponse rsp = cache.get(key); assertEquals(MomentoCacheResult.Hit, rsp.getResult()); - assertEquals("bar", StandardCharsets.US_ASCII.decode(rsp.getBody()).toString()); + assertEquals("bar", rsp.asStringUtf8()); } @Test @@ -131,10 +131,10 @@ private static void testAsyncHappyPath(Cache client) throws Exception { assertEquals(MomentoCacheResult.Ok, setRsp.toCompletableFuture().get().getResult()); // Get Key Async - ClientGetResponse rsp = client.getAsync(key).toCompletableFuture().get(); + ClientGetResponse rsp = client.getAsync(key).toCompletableFuture().get(); assertEquals(MomentoCacheResult.Hit, rsp.getResult()); - assertEquals("bar", StandardCharsets.US_ASCII.decode(rsp.getBody()).toString()); + assertEquals("bar", rsp.asStringUtf8()); } @Test @@ -165,7 +165,7 @@ private static void testTtlHappyPath(Cache client) throws Exception { Thread.sleep(1500); // Get Key that was just set - ClientGetResponse rsp = client.get(key); + ClientGetResponse rsp = client.get(key); assertEquals(MomentoCacheResult.Miss, rsp.getResult()); } @@ -188,7 +188,7 @@ void testMissHappyPathWithTracing() throws Exception { private static void testMissHappyPathInternal(Cache client) { // Get Key that was just set - ClientGetResponse rsp = client.get(UUID.randomUUID().toString()); + ClientGetResponse rsp = client.get(UUID.randomUUID().toString()); assertEquals(MomentoCacheResult.Miss, rsp.getResult()); } diff --git a/momento-sdk/src/intTest/java/momento/sdk/MomentoTest.java b/momento-sdk/src/intTest/java/momento/sdk/MomentoTest.java index a20a7b59..569bb958 100644 --- a/momento-sdk/src/intTest/java/momento/sdk/MomentoTest.java +++ b/momento-sdk/src/intTest/java/momento/sdk/MomentoTest.java @@ -56,9 +56,9 @@ void testHappyPath() { assertEquals(MomentoCacheResult.Ok, setRsp.getResult()); // Get Key that was just set - ClientGetResponse rsp = cache.get(key); + ClientGetResponse rsp = cache.get(key); assertEquals(MomentoCacheResult.Hit, rsp.getResult()); - assertEquals("bar", StandardCharsets.US_ASCII.decode(rsp.getBody()).toString()); + assertEquals("bar", rsp.asStringUtf8()); } // TODO: Update this to be recreated each time and add a separate test case for Already Exists diff --git a/momento-sdk/src/main/java/momento/sdk/Cache.java b/momento-sdk/src/main/java/momento/sdk/Cache.java index 2dfc6e3b..7db361e5 100644 --- a/momento-sdk/src/main/java/momento/sdk/Cache.java +++ b/momento-sdk/src/main/java/momento/sdk/Cache.java @@ -24,7 +24,6 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.ImplicitContextKeyed; import io.opentelemetry.context.Scope; -import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; @@ -36,7 +35,6 @@ import java.util.concurrent.CompletionStage; import javax.net.ssl.SSLException; import momento.sdk.exceptions.CacheServiceExceptionMapper; -import momento.sdk.exceptions.ClientSdkException; import momento.sdk.messages.ClientGetResponse; import momento.sdk.messages.ClientSetResponse; @@ -135,17 +133,15 @@ public Cache( * java.util.concurrent.CompletionStage} returned instead. * * @param key the key of item to fetch from cache - * @return {@link ClientGetResponse} with the response object as a {@link java.nio.ByteBuffer} + * @return {@link ClientGetResponse} with the response object * @throws IOException if an error occurs opening input stream for response body. */ - public ClientGetResponse get(String key) { + public ClientGetResponse get(String key) { Optional span = buildSpan("java-sdk-get-request"); try (Scope ignored = (span.map(ImplicitContextKeyed::makeCurrent).orElse(null))) { GetResponse rsp = blockingStub.get(buildGetRequest(key)); - - ByteBuffer body = rsp.getCacheBody().asReadOnlyByteBuffer(); - ClientGetResponse clientGetResponse = - new ClientGetResponse<>(rsp.getResult(), body); + ClientGetResponse clientGetResponse = + new ClientGetResponse(rsp.getResult(), rsp.getCacheBody()); span.ifPresent(theSpan -> theSpan.setStatus(StatusCode.OK)); return clientGetResponse; } catch (Exception e) { @@ -172,6 +168,18 @@ public ClientGetResponse get(String key) { * @throws IOException if an error occurs opening ByteBuffer for request body. */ public ClientSetResponse set(String key, ByteBuffer value, int ttlSeconds) { + return set(convert(key), convert(value), ttlSeconds); + } + + public ClientSetResponse set(String key, String value, int ttlSeconds) { + return set(convert(key), convert(value), ttlSeconds); + } + + public ClientSetResponse set(byte[] key, byte[] value, int ttlSeconds) { + return set(convert(key), convert(value), ttlSeconds); + } + + private ClientSetResponse set(ByteString key, ByteString value, int ttlSeconds) { Optional span = buildSpan("java-sdk-set-request"); try (Scope ignored = (span.map(ImplicitContextKeyed::makeCurrent).orElse(null))) { SetResponse rsp = blockingStub.set(buildSetRequest(key, value, ttlSeconds * 1000)); @@ -200,15 +208,15 @@ public ClientSetResponse set(String key, ByteBuffer value, int ttlSeconds) { * CompletionStage interface wrapping standard ClientResponse with response object as a {@link * java.io.InputStream}. */ - public CompletionStage> getAsync(String key) { + public CompletionStage getAsync(String key) { Optional span = buildSpan("java-sdk-get-request"); Optional scope = (span.map(ImplicitContextKeyed::makeCurrent)); // Submit request to non-blocking stub ListenableFuture rspFuture = futureStub.get(buildGetRequest(key)); // Build a CompletableFuture to return to caller - CompletableFuture> returnFuture = - new CompletableFuture>() { + CompletableFuture returnFuture = + new CompletableFuture() { @Override public boolean cancel(boolean mayInterruptIfRunning) { // propagate cancel to the listenable future if called on returned completable future @@ -224,8 +232,7 @@ public boolean cancel(boolean mayInterruptIfRunning) { new FutureCallback() { @Override public void onSuccess(GetResponse rsp) { - ByteBuffer body = rsp.getCacheBody().asReadOnlyByteBuffer(); - returnFuture.complete(new ClientGetResponse<>(rsp.getResult(), body)); + returnFuture.complete(new ClientGetResponse(rsp.getResult(), rsp.getCacheBody())); span.ifPresent( theSpan -> { theSpan.setStatus(StatusCode.OK); @@ -265,6 +272,7 @@ public void onFailure(Throwable e) { * CompletionStage interface wrapping standard ClientSetResponse. * @throws IOException if an error occurs opening ByteBuffer for request body. */ + // TODO: Update Async methods to support different input params. public CompletionStage setAsync(String key, ByteBuffer value, int ttlSeconds) { Optional span = buildSpan("java-sdk-set-request"); @@ -272,7 +280,7 @@ public CompletionStage setAsync(String key, ByteBuffer value, // Submit request to non-blocking stub ListenableFuture rspFuture = - futureStub.set(buildSetRequest(key, value, ttlSeconds * 1000)); + futureStub.set(buildSetRequest(convert(key), convert(value), ttlSeconds * 1000)); // Build a CompletableFuture to return to caller CompletableFuture returnFuture = @@ -331,16 +339,24 @@ private GetRequest buildGetRequest(String key) { .build(); } - private SetRequest buildSetRequest(String key, ByteBuffer value, int ttl) { - try { - return SetRequest.newBuilder() - .setCacheKey(ByteString.copyFrom(key, StandardCharsets.UTF_8)) - .setCacheBody(ByteString.readFrom(new ByteArrayInputStream(value.array()))) - .setTtlMilliseconds(ttl) - .build(); - } catch (IOException e) { - throw new ClientSdkException("Failed to create request."); - } + private SetRequest buildSetRequest(ByteString key, ByteString value, int ttl) { + return SetRequest.newBuilder() + .setCacheKey(key) + .setCacheBody(value) + .setTtlMilliseconds(ttl) + .build(); + } + + private ByteString convert(String stringToEncode) { + return ByteString.copyFromUtf8(stringToEncode); + } + + private ByteString convert(byte[] bytes) { + return ByteString.copyFrom(bytes); + } + + private ByteString convert(ByteBuffer byteBuffer) { + return ByteString.copyFrom(byteBuffer); } private Optional buildSpan(String spanName) { diff --git a/momento-sdk/src/main/java/momento/sdk/exceptions/SdkException.java b/momento-sdk/src/main/java/momento/sdk/exceptions/SdkException.java index 6989b554..7157374e 100644 --- a/momento-sdk/src/main/java/momento/sdk/exceptions/SdkException.java +++ b/momento-sdk/src/main/java/momento/sdk/exceptions/SdkException.java @@ -5,7 +5,6 @@ public class SdkException extends RuntimeException { public SdkException(String message, Throwable cause) { super(message, cause); - this.initCause(cause); } public SdkException(String message) { diff --git a/momento-sdk/src/main/java/momento/sdk/messages/ClientGetResponse.java b/momento-sdk/src/main/java/momento/sdk/messages/ClientGetResponse.java index eeacdec8..a2130ce1 100644 --- a/momento-sdk/src/main/java/momento/sdk/messages/ClientGetResponse.java +++ b/momento-sdk/src/main/java/momento/sdk/messages/ClientGetResponse.java @@ -1,12 +1,14 @@ package momento.sdk.messages; +import com.google.protobuf.ByteString; import grpc.cache_client.ECacheResult; +import java.nio.ByteBuffer; -public final class ClientGetResponse extends BaseResponse { - private final T body; +public final class ClientGetResponse extends BaseResponse { + private final ByteString body; private final ECacheResult result; - public ClientGetResponse(ECacheResult result, T body) { + public ClientGetResponse(ECacheResult result, ByteString body) { this.body = body; this.result = result; } @@ -15,7 +17,20 @@ public MomentoCacheResult getResult() { return this.resultMapper(this.result); } - public T getBody() { - return body; + public byte[] asByteArray() { + return body.toByteArray(); + } + + public ByteBuffer asByteBuffer() { + return body.asReadOnlyByteBuffer(); + } + + /** + * Converts the value read from cache to a UTF-8 String + * + * @return + */ + public String asStringUtf8() { + return body.toStringUtf8(); } }