This is an automated email from the ASF dual-hosted git repository. gosullivan pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/develop by this push: new 6214a43 GEODE-2999: Add PutIfAbsent to the Protobuf protocol. (#1578) 6214a43 is described below commit 6214a43be9d31fd0be1d133a8f0ad7379ea3f9c2 Author: Galen O'Sullivan <gosulli...@pivotal.io> AuthorDate: Tue Mar 13 09:34:23 2018 -0700 GEODE-2999: Add PutIfAbsent to the Protobuf protocol. (#1578) * Some test cleanup * Add it to the experimental protobuf client driver --- .../geode/experimental/driver/ProtobufRegion.java | 14 ++ .../apache/geode/experimental/driver/Region.java | 11 ++ .../experimental/driver/RegionIntegrationTest.java | 23 +++ .../src/main/proto/v1/basicTypes.proto | 1 + .../src/main/proto/v1/clientProtocol.proto | 3 + .../src/main/proto/v1/region_API.proto | 9 ++ .../protocol/protobuf/v1/ProtobufOpsProcessor.java | 6 +- .../protobuf/v1/ProtobufStreamProcessor.java | 1 - .../PutIfAbsentRequestOperationHandler.java | 83 ++++++++++ .../registry/ProtobufOperationContextRegistry.java | 38 +++-- .../protobuf/v1/utilities/ProtobufUtilities.java | 14 -- .../internal/protocol/protobuf/v1/MessageUtil.java | 17 +++ .../protobuf/v1/ProtobufRequestUtilities.java | 19 ++- .../v1/acceptance/CacheConnectionJUnitTest.java | 15 +- .../v1/acceptance/CacheOperationsJUnitTest.java | 20 +-- .../PutIfAbsentRequestIntegrationTest.java | 168 ++++++++++++++++++++ ...utIfAbsentRequestOperationHandlerJUnitTest.java | 169 +++++++++++++++++++++ 17 files changed, 545 insertions(+), 66 deletions(-) diff --git a/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/ProtobufRegion.java b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/ProtobufRegion.java index 2870687..a7fc501 100644 --- a/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/ProtobufRegion.java +++ b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/ProtobufRegion.java @@ -148,6 +148,20 @@ public class ProtobufRegion<K, V> implements Region<K, V> { } @Override + public V putIfAbsent(K key, V value) throws IOException { + final RegionAPI.PutIfAbsentRequest.Builder putIfAbsentRequest = RegionAPI.PutIfAbsentRequest + .newBuilder().setRegionName(name).setEntry(ValueEncoder.encodeEntry(key, value)); + + final Message request = Message.newBuilder().setPutIfAbsentRequest(putIfAbsentRequest).build(); + + final RegionAPI.PutIfAbsentResponse putIfAbsentResponse = protobufChannel + .sendRequest(request, MessageTypeCase.PUTIFABSENTRESPONSE).getPutIfAbsentResponse(); + + + return (V) ValueEncoder.decodeValue(putIfAbsentResponse.getOldValue()); + } + + @Override public void remove(K key) throws IOException { final Message request = Message.newBuilder() .setRemoveRequest( diff --git a/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/Region.java b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/Region.java index bbdbe2d..32cb381 100644 --- a/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/Region.java +++ b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/Region.java @@ -86,6 +86,17 @@ public interface Region<K, V> { void clear() throws IOException; /** + * Puts the <code>value</code> into this region for the <code>key</code> if <code>key</code> does + * not already have a value associated with it. + * + * @return null if the value was set; the current value otherwise. + * NOTE that if the value in the region was set to null, this method will return null + * without setting a new value. + * @throws IOException + */ + V putIfAbsent(K key, V value) throws IOException; + + /** * Removes any value associated with the <code>key</code> from this region. * * @param key Unique key associated with a value. diff --git a/geode-experimental-driver/src/test/java/org/apache/geode/experimental/driver/RegionIntegrationTest.java b/geode-experimental-driver/src/test/java/org/apache/geode/experimental/driver/RegionIntegrationTest.java index 78af30c..6782ec2 100644 --- a/geode-experimental-driver/src/test/java/org/apache/geode/experimental/driver/RegionIntegrationTest.java +++ b/geode-experimental-driver/src/test/java/org/apache/geode/experimental/driver/RegionIntegrationTest.java @@ -95,6 +95,29 @@ public class RegionIntegrationTest extends IntegrationTestBase { } @Test + public void putIfAbsent() throws Exception { + Region<JSONWrapper, JSONWrapper> region = driver.getRegion("region"); + JSONWrapper document = JSONWrapper.wrapJSON(jsonDocument); + + assertNull(region.putIfAbsent(document, document)); + + JSONWrapper value = region.get(document); + assertEquals(document, value); + assertEquals(1, serverRegion.size()); + + assertEquals(document, region.putIfAbsent(document, JSONWrapper.wrapJSON("{3 : 2}"))); + value = region.get(document); + assertEquals(document, value); + assertEquals(1, serverRegion.size()); + + org.apache.geode.cache.Region.Entry entry = + (org.apache.geode.cache.Region.Entry) serverRegion.entrySet().iterator().next(); + + assertTrue(PdxInstance.class.isAssignableFrom(entry.getKey().getClass())); + assertTrue(PdxInstance.class.isAssignableFrom(entry.getValue().getClass())); + } + + @Test public void removeWithJSONKey() throws Exception { Region<JSONWrapper, JSONWrapper> region = driver.getRegion("region"); JSONWrapper document = JSONWrapper.wrapJSON(jsonDocument); diff --git a/geode-protobuf-messages/src/main/proto/v1/basicTypes.proto b/geode-protobuf-messages/src/main/proto/v1/basicTypes.proto index a12718b..e8cfa15 100644 --- a/geode-protobuf-messages/src/main/proto/v1/basicTypes.proto +++ b/geode-protobuf-messages/src/main/proto/v1/basicTypes.proto @@ -82,6 +82,7 @@ enum ErrorCode { AUTHENTICATION_NOT_SUPPORTED = 13; AUTHORIZATION_FAILED = 20; INVALID_REQUEST = 50; + UNSUPPORTED_OPERATION = 60; SERVER_ERROR = 100; NO_AVAILABLE_SERVER = 101; } diff --git a/geode-protobuf-messages/src/main/proto/v1/clientProtocol.proto b/geode-protobuf-messages/src/main/proto/v1/clientProtocol.proto index 2b21c89..c3b0f5f 100644 --- a/geode-protobuf-messages/src/main/proto/v1/clientProtocol.proto +++ b/geode-protobuf-messages/src/main/proto/v1/clientProtocol.proto @@ -78,6 +78,9 @@ message Message { ClearRequest clearRequest = 32; ClearResponse clearResponse = 33; + + PutIfAbsentRequest putIfAbsentRequest = 34; + PutIfAbsentResponse putIfAbsentResponse = 35; } } diff --git a/geode-protobuf-messages/src/main/proto/v1/region_API.proto b/geode-protobuf-messages/src/main/proto/v1/region_API.proto index 407409f..b70765b 100644 --- a/geode-protobuf-messages/src/main/proto/v1/region_API.proto +++ b/geode-protobuf-messages/src/main/proto/v1/region_API.proto @@ -32,6 +32,15 @@ message PutResponse { // message presence indicates success. } +message PutIfAbsentRequest { + string regionName = 1; + Entry entry = 2; +} + +message PutIfAbsentResponse { + EncodedValue oldValue = 1; +} + message GetRequest { string regionName = 1; EncodedValue key = 2; diff --git a/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/ProtobufOpsProcessor.java b/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/ProtobufOpsProcessor.java index 1f2d201..cfc71b3 100644 --- a/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/ProtobufOpsProcessor.java +++ b/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/ProtobufOpsProcessor.java @@ -25,7 +25,6 @@ import org.apache.geode.internal.protocol.protobuf.v1.serialization.exception.De import org.apache.geode.internal.protocol.protobuf.v1.serialization.exception.EncodingException; import org.apache.geode.internal.protocol.protobuf.v1.state.ProtobufConnectionTerminatingStateProcessor; import org.apache.geode.internal.protocol.protobuf.v1.state.exception.ConnectionStateException; -import org.apache.geode.internal.protocol.protobuf.v1.state.exception.OperationNotAuthorizedException; /** * This handles protobuf requests by determining the operation type of the request and dispatching @@ -86,6 +85,11 @@ public class ProtobufOpsProcessor { logger.error(exception); return Failure.of(BasicTypes.ErrorCode.INVALID_REQUEST, "Invalid execution context found for operation."); + } catch (UnsupportedOperationException exception) { + logger.error("Unsupported operation exception for request {}", requestType); + logger.error(exception); + return Failure.of(BasicTypes.ErrorCode.UNSUPPORTED_OPERATION, + "Unsupported operation:" + exception.getMessage()); } finally { context.getStatistics().endOperation(startTime); } diff --git a/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/ProtobufStreamProcessor.java b/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/ProtobufStreamProcessor.java index e72774f..1558ee0 100644 --- a/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/ProtobufStreamProcessor.java +++ b/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/ProtobufStreamProcessor.java @@ -27,7 +27,6 @@ import org.apache.geode.internal.protocol.protobuf.statistics.ClientStatistics; import org.apache.geode.internal.protocol.protobuf.v1.registry.ProtobufOperationContextRegistry; import org.apache.geode.internal.protocol.protobuf.v1.serializer.ProtobufProtocolSerializer; import org.apache.geode.internal.protocol.protobuf.v1.serializer.exception.InvalidProtocolMessageException; -import org.apache.geode.internal.protocol.protobuf.v1.utilities.ProtobufUtilities; /** * This object handles an incoming stream containing protobuf messages. It parses the protobuf diff --git a/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/operations/PutIfAbsentRequestOperationHandler.java b/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/operations/PutIfAbsentRequestOperationHandler.java new file mode 100644 index 0000000..47b0d0a --- /dev/null +++ b/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/operations/PutIfAbsentRequestOperationHandler.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.internal.protocol.protobuf.v1.operations; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.apache.geode.cache.Region; +import org.apache.geode.internal.exception.InvalidExecutionContextException; +import org.apache.geode.internal.protocol.operations.ProtobufOperationHandler; +import org.apache.geode.internal.protocol.protobuf.v1.BasicTypes; +import org.apache.geode.internal.protocol.protobuf.v1.Failure; +import org.apache.geode.internal.protocol.protobuf.v1.MessageExecutionContext; +import org.apache.geode.internal.protocol.protobuf.v1.ProtobufSerializationService; +import org.apache.geode.internal.protocol.protobuf.v1.RegionAPI; +import org.apache.geode.internal.protocol.protobuf.v1.Result; +import org.apache.geode.internal.protocol.protobuf.v1.Success; +import org.apache.geode.internal.protocol.protobuf.v1.serialization.exception.DecodingException; +import org.apache.geode.internal.protocol.protobuf.v1.serialization.exception.EncodingException; +import org.apache.geode.internal.protocol.protobuf.v1.state.exception.ConnectionStateException; +import org.apache.geode.security.ResourcePermission; + +public class PutIfAbsentRequestOperationHandler implements + ProtobufOperationHandler<RegionAPI.PutIfAbsentRequest, RegionAPI.PutIfAbsentResponse> { + private static final Logger logger = LogManager.getLogger(); + + @Override + public Result<RegionAPI.PutIfAbsentResponse> process( + ProtobufSerializationService serializationService, RegionAPI.PutIfAbsentRequest request, + MessageExecutionContext messageExecutionContext) throws InvalidExecutionContextException, + ConnectionStateException, EncodingException, DecodingException { + + final String regionName = request.getRegionName(); + + Region<Object, Object> region; + try { + region = messageExecutionContext.getCache().getRegion(regionName); + } catch (IllegalArgumentException ex) { + return Failure.of(BasicTypes.ErrorCode.INVALID_REQUEST, + "Invalid region name: \"" + regionName + "\""); + } + + if (region == null) { + logger.error("Received PutIfAbsentRequest for nonexistent region: {}", regionName); + return Failure.of(BasicTypes.ErrorCode.SERVER_ERROR, + "Region \"" + regionName + "\" not found"); + } + + final BasicTypes.Entry entry = request.getEntry(); + + Object decodedValue = serializationService.decode(entry.getValue()); + Object decodedKey = serializationService.decode(entry.getKey()); + + if (decodedKey == null || decodedValue == null) { + return Failure.of(BasicTypes.ErrorCode.INVALID_REQUEST, + "Key and value must both be non-NULL"); + } + + final Object oldValue = region.putIfAbsent(decodedKey, decodedValue); + + return Success.of(RegionAPI.PutIfAbsentResponse.newBuilder() + .setOldValue(serializationService.encode(oldValue)).build()); + } + + public static ResourcePermission determineRequiredPermission(RegionAPI.PutIfAbsentRequest request, + ProtobufSerializationService serializer) throws DecodingException { + return new ResourcePermission(ResourcePermission.Resource.DATA, + ResourcePermission.Operation.WRITE, request.getRegionName(), + serializer.decode(request.getEntry().getKey()).toString()); + } +} diff --git a/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/registry/ProtobufOperationContextRegistry.java b/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/registry/ProtobufOperationContextRegistry.java index d950baa..64a5cba 100644 --- a/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/registry/ProtobufOperationContextRegistry.java +++ b/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/registry/ProtobufOperationContextRegistry.java @@ -36,6 +36,7 @@ import org.apache.geode.internal.protocol.protobuf.v1.operations.GetSizeRequestO import org.apache.geode.internal.protocol.protobuf.v1.operations.KeySetOperationHandler; import org.apache.geode.internal.protocol.protobuf.v1.operations.OqlQueryRequestOperationHandler; import org.apache.geode.internal.protocol.protobuf.v1.operations.PutAllRequestOperationHandler; +import org.apache.geode.internal.protocol.protobuf.v1.operations.PutIfAbsentRequestOperationHandler; import org.apache.geode.internal.protocol.protobuf.v1.operations.PutRequestOperationHandler; import org.apache.geode.internal.protocol.protobuf.v1.operations.RemoveRequestOperationHandler; import org.apache.geode.internal.protocol.protobuf.v1.operations.security.AuthenticationRequestOperationHandler; @@ -46,15 +47,14 @@ import org.apache.geode.security.ResourcePermission.Resource; @Experimental public class ProtobufOperationContextRegistry { - private Map<ClientProtocol.Message.MessageTypeCase, ProtobufOperationContext> operationContexts = + private final Map<MessageTypeCase, ProtobufOperationContext> operationContexts = new ConcurrentHashMap<>(); public ProtobufOperationContextRegistry() { addContexts(); } - public ProtobufOperationContext getOperationContext( - ClientProtocol.Message.MessageTypeCase apiCase) { + public ProtobufOperationContext getOperationContext(MessageTypeCase apiCase) { return operationContexts.get(apiCase); } @@ -67,69 +67,69 @@ public class ProtobufOperationContextRegistry { } private void addContexts() { - operationContexts.put(ClientProtocol.Message.MessageTypeCase.AUTHENTICATIONREQUEST, + operationContexts.put(MessageTypeCase.AUTHENTICATIONREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getAuthenticationRequest, new AuthenticationRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder().setAuthenticationResponse(opsResp), this::skipAuthorizationCheck)); - operationContexts.put(ClientProtocol.Message.MessageTypeCase.DISCONNECTCLIENTREQUEST, + operationContexts.put(MessageTypeCase.DISCONNECTCLIENTREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getDisconnectClientRequest, new DisconnectClientRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder().setDisconnectClientResponse(opsResp), this::skipAuthorizationCheck)); - operationContexts.put(ClientProtocol.Message.MessageTypeCase.GETREQUEST, + operationContexts.put(MessageTypeCase.GETREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getGetRequest, new GetRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder().setGetResponse(opsResp), GetRequestOperationHandler::determineRequiredPermission)); - operationContexts.put(ClientProtocol.Message.MessageTypeCase.GETALLREQUEST, + operationContexts.put(MessageTypeCase.GETALLREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getGetAllRequest, new GetAllRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder().setGetAllResponse(opsResp), // May require per-key checks, will be handled by OperationHandler this::skipAuthorizationCheck)); - operationContexts.put(ClientProtocol.Message.MessageTypeCase.PUTREQUEST, + operationContexts.put(MessageTypeCase.PUTREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getPutRequest, new PutRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder().setPutResponse(opsResp), PutRequestOperationHandler::determineRequiredPermission)); - operationContexts.put(ClientProtocol.Message.MessageTypeCase.PUTALLREQUEST, + operationContexts.put(MessageTypeCase.PUTALLREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getPutAllRequest, new PutAllRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder().setPutAllResponse(opsResp), // May require per-key checks, will be handled by OperationHandler this::skipAuthorizationCheck)); - operationContexts.put(ClientProtocol.Message.MessageTypeCase.REMOVEREQUEST, + operationContexts.put(MessageTypeCase.REMOVEREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getRemoveRequest, new RemoveRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder().setRemoveResponse(opsResp), RemoveRequestOperationHandler::determineRequiredPermission)); - operationContexts.put(ClientProtocol.Message.MessageTypeCase.GETREGIONNAMESREQUEST, + operationContexts.put(MessageTypeCase.GETREGIONNAMESREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getGetRegionNamesRequest, new GetRegionNamesRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder().setGetRegionNamesResponse(opsResp), ResourcePermissions.DATA_READ)); - operationContexts.put(ClientProtocol.Message.MessageTypeCase.GETSIZEREQUEST, + operationContexts.put(MessageTypeCase.GETSIZEREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getGetSizeRequest, new GetSizeRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder().setGetSizeResponse(opsResp), ResourcePermissions.DATA_READ)); - operationContexts.put(ClientProtocol.Message.MessageTypeCase.GETSERVERREQUEST, + operationContexts.put(MessageTypeCase.GETSERVERREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getGetServerRequest, new GetServerOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder().setGetServerResponse(opsResp), ResourcePermissions.CLUSTER_READ)); - operationContexts.put(ClientProtocol.Message.MessageTypeCase.EXECUTEFUNCTIONONREGIONREQUEST, + operationContexts.put(MessageTypeCase.EXECUTEFUNCTIONONREGIONREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getExecuteFunctionOnRegionRequest, new ExecuteFunctionOnRegionRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder() @@ -138,7 +138,7 @@ public class ProtobufOperationContextRegistry { // requirements. this::skipAuthorizationCheck)); - operationContexts.put(ClientProtocol.Message.MessageTypeCase.EXECUTEFUNCTIONONMEMBERREQUEST, + operationContexts.put(MessageTypeCase.EXECUTEFUNCTIONONMEMBERREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getExecuteFunctionOnMemberRequest, new ExecuteFunctionOnMemberRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder() @@ -147,7 +147,7 @@ public class ProtobufOperationContextRegistry { // requirements. this::skipAuthorizationCheck)); - operationContexts.put(ClientProtocol.Message.MessageTypeCase.EXECUTEFUNCTIONONGROUPREQUEST, + operationContexts.put(MessageTypeCase.EXECUTEFUNCTIONONGROUPREQUEST, new ProtobufOperationContext<>(ClientProtocol.Message::getExecuteFunctionOnGroupRequest, new ExecuteFunctionOnGroupRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder() @@ -174,5 +174,11 @@ public class ProtobufOperationContextRegistry { new ClearRequestOperationHandler(), opsResp -> ClientProtocol.Message.newBuilder().setClearResponse(opsResp), ClearRequestOperationHandler::determineRequiredPermission)); + + operationContexts.put(MessageTypeCase.PUTIFABSENTREQUEST, + new ProtobufOperationContext<>(ClientProtocol.Message::getPutIfAbsentRequest, + new PutIfAbsentRequestOperationHandler(), + opsResp -> ClientProtocol.Message.newBuilder().setPutIfAbsentResponse(opsResp), + PutIfAbsentRequestOperationHandler::determineRequiredPermission)); } } diff --git a/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/utilities/ProtobufUtilities.java b/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/utilities/ProtobufUtilities.java index ea39ca8..844f1eb 100644 --- a/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/utilities/ProtobufUtilities.java +++ b/geode-protobuf/src/main/java/org/apache/geode/internal/protocol/protobuf/v1/utilities/ProtobufUtilities.java @@ -16,9 +16,7 @@ package org.apache.geode.internal.protocol.protobuf.v1.utilities; import org.apache.geode.annotations.Experimental; import org.apache.geode.internal.protocol.protobuf.v1.BasicTypes; -import org.apache.geode.internal.protocol.protobuf.v1.ClientProtocol; import org.apache.geode.internal.protocol.protobuf.v1.ProtobufSerializationService; -import org.apache.geode.internal.protocol.protobuf.v1.RegionAPI; import org.apache.geode.internal.protocol.protobuf.v1.serialization.exception.EncodingException; /** @@ -26,8 +24,6 @@ import org.apache.geode.internal.protocol.protobuf.v1.serialization.exception.En * mainly focused on helper functions which can be used in building BasicTypes for use in other * messages or those used to create the top level Message objects. * <p> - * Helper functions specific to creating ClientProtocol.Messages can be found at - * {@link ProtobufRequestUtilities} */ @Experimental public abstract class ProtobufUtilities { @@ -67,14 +63,4 @@ public abstract class ProtobufUtilities { serializationService.encode(unencodedValue)); } - /** - * This creates a protobuf message containing a ClientProtocol.Message - * - * @param getAllRequest - The request for the message - * @return a protobuf Message containing the above parameters - */ - public static ClientProtocol.Message createProtobufRequestWithGetAllRequest( - RegionAPI.GetAllRequest getAllRequest) { - return ClientProtocol.Message.newBuilder().setGetAllRequest(getAllRequest).build(); - } } diff --git a/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/MessageUtil.java b/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/MessageUtil.java index 7080202..0e83b93 100644 --- a/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/MessageUtil.java +++ b/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/MessageUtil.java @@ -14,6 +14,7 @@ */ package org.apache.geode.internal.protocol.protobuf.v1; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; @@ -24,7 +25,10 @@ import java.net.Socket; import com.google.protobuf.MessageLite; import org.apache.geode.internal.protocol.protobuf.ProtocolVersion; +import org.apache.geode.internal.protocol.protobuf.v1.ProtobufRequestUtilities; import org.apache.geode.internal.protocol.protobuf.v1.serialization.exception.EncodingException; +import org.apache.geode.internal.protocol.protobuf.v1.serializer.ProtobufProtocolSerializer; +import org.apache.geode.internal.protocol.protobuf.v1.serializer.exception.InvalidProtocolMessageException; import org.apache.geode.internal.protocol.protobuf.v1.utilities.ProtobufUtilities; public class MessageUtil { @@ -89,4 +93,17 @@ public class MessageUtil { throw new RuntimeException(e); // never happens. } } + + public static void validateGetResponse(Socket socket, + ProtobufProtocolSerializer protobufProtocolSerializer, Object expectedValue) + throws InvalidProtocolMessageException, IOException { + + ClientProtocol.Message response = + protobufProtocolSerializer.deserialize(socket.getInputStream()); + assertEquals(ClientProtocol.Message.MessageTypeCase.GETRESPONSE, response.getMessageTypeCase()); + RegionAPI.GetResponse getResponse = response.getGetResponse(); + BasicTypes.EncodedValue result = getResponse.getResult(); + assertEquals(BasicTypes.EncodedValue.ValueCase.STRINGRESULT, result.getValueCase()); + assertEquals(expectedValue, result.getStringResult()); + } } diff --git a/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/ProtobufRequestUtilities.java b/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/ProtobufRequestUtilities.java index 873fe02..2faa4bb 100644 --- a/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/ProtobufRequestUtilities.java +++ b/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/ProtobufRequestUtilities.java @@ -17,10 +17,7 @@ package org.apache.geode.internal.protocol.protobuf.v1; import java.util.Set; import org.apache.geode.annotations.Experimental; -import org.apache.geode.internal.protocol.protobuf.v1.BasicTypes; -import org.apache.geode.internal.protocol.protobuf.v1.ClientProtocol; -import org.apache.geode.internal.protocol.protobuf.v1.LocatorAPI; -import org.apache.geode.internal.protocol.protobuf.v1.RegionAPI; +import org.apache.geode.internal.protocol.protobuf.v1.utilities.ProtobufUtilities; /** * This class contains helper functions for generating ClientProtocol.Message objects @@ -92,6 +89,20 @@ public abstract class ProtobufRequestUtilities { } /** + * Creates a request object containing a RegionAPI.PutIfAbsentRequest + * + * @param region - Name of the region to put data in + * @param entry - Encoded key,value pair, see createEntry in {@link ProtobufRequestUtilities} + * @return Request object containing the passed params. + */ + public static ClientProtocol.Message createPutIfAbsentRequest(String region, + BasicTypes.Entry entry) { + RegionAPI.PutIfAbsentRequest putIfAbsentRequest = + RegionAPI.PutIfAbsentRequest.newBuilder().setRegionName(region).setEntry(entry).build(); + return ClientProtocol.Message.newBuilder().setPutIfAbsentRequest(putIfAbsentRequest).build(); + } + + /** * Create a request to get the values for multiple keys * * @param regionName - Name of the region to fetch from diff --git a/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/acceptance/CacheConnectionJUnitTest.java b/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/acceptance/CacheConnectionJUnitTest.java index bfb1ff8..677d966 100644 --- a/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/acceptance/CacheConnectionJUnitTest.java +++ b/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/acceptance/CacheConnectionJUnitTest.java @@ -22,6 +22,7 @@ import static org.apache.geode.distributed.ConfigurationProperties.SSL_KEYSTORE_ import static org.apache.geode.distributed.ConfigurationProperties.SSL_REQUIRE_AUTHENTICATION; import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE; import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE_PASSWORD; +import static org.apache.geode.internal.protocol.protobuf.v1.MessageUtil.validateGetResponse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -61,11 +62,9 @@ import org.apache.geode.internal.cache.tier.sockets.AcceptorImpl; import org.apache.geode.internal.net.SocketCreator; import org.apache.geode.internal.net.SocketCreatorFactory; import org.apache.geode.internal.protocol.protobuf.statistics.ProtobufClientStatistics; -import org.apache.geode.internal.protocol.protobuf.v1.BasicTypes; import org.apache.geode.internal.protocol.protobuf.v1.ClientProtocol; import org.apache.geode.internal.protocol.protobuf.v1.MessageUtil; import org.apache.geode.internal.protocol.protobuf.v1.ProtobufSerializationService; -import org.apache.geode.internal.protocol.protobuf.v1.RegionAPI; import org.apache.geode.internal.protocol.protobuf.v1.serializer.ProtobufProtocolSerializer; import org.apache.geode.internal.protocol.protobuf.v1.serializer.exception.InvalidProtocolMessageException; import org.apache.geode.test.junit.categories.IntegrationTest; @@ -215,18 +214,6 @@ public class CacheConnectionJUnitTest { assertEquals(ClientProtocol.Message.MessageTypeCase.PUTRESPONSE, response.getMessageTypeCase()); } - private void validateGetResponse(Socket socket, - ProtobufProtocolSerializer protobufProtocolSerializer, Object expectedValue) - throws InvalidProtocolMessageException, IOException { - ClientProtocol.Message response = deserializeResponse(socket, protobufProtocolSerializer); - - assertEquals(ClientProtocol.Message.MessageTypeCase.GETRESPONSE, response.getMessageTypeCase()); - RegionAPI.GetResponse getResponse = response.getGetResponse(); - BasicTypes.EncodedValue result = getResponse.getResult(); - assertEquals(BasicTypes.EncodedValue.ValueCase.STRINGRESULT, result.getValueCase()); - assertEquals(expectedValue, result.getStringResult()); - } - private ClientProtocol.Message deserializeResponse(Socket socket, ProtobufProtocolSerializer protobufProtocolSerializer) throws InvalidProtocolMessageException, IOException { diff --git a/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/acceptance/CacheOperationsJUnitTest.java b/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/acceptance/CacheOperationsJUnitTest.java index a997590..4859837 100644 --- a/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/acceptance/CacheOperationsJUnitTest.java +++ b/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/acceptance/CacheOperationsJUnitTest.java @@ -22,6 +22,7 @@ import static org.apache.geode.distributed.ConfigurationProperties.SSL_KEYSTORE_ import static org.apache.geode.distributed.ConfigurationProperties.SSL_REQUIRE_AUTHENTICATION; import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE; import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE_PASSWORD; +import static org.apache.geode.internal.protocol.protobuf.v1.MessageUtil.validateGetResponse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -101,6 +102,7 @@ public class CacheOperationsJUnitTest { @Rule public TestName testName = new TestName(); + private ProtobufProtocolSerializer protobufProtocolSerializer; @Before public void setup() throws Exception { @@ -139,6 +141,7 @@ public class CacheOperationsJUnitTest { MessageUtil.performAndVerifyHandshake(socket); serializationService = new ProtobufSerializationService(); + protobufProtocolSerializer = new ProtobufProtocolSerializer(); } @After @@ -174,7 +177,7 @@ public class CacheOperationsJUnitTest { ProtobufRequestUtilities.createGetAllRequest(TEST_REGION, getEntries); ClientProtocol.Message getAllMessage = - ProtobufUtilities.createProtobufRequestWithGetAllRequest(getAllRequest); + ClientProtocol.Message.newBuilder().setGetAllRequest(getAllRequest).build(); protobufProtocolSerializer.serialize(getAllMessage, outputStream); validateGetAllResponse(socket, protobufProtocolSerializer); @@ -194,7 +197,6 @@ public class CacheOperationsJUnitTest { regionFactory.create(regionName); System.setProperty("geode.feature-protobuf-protocol", "true"); - ProtobufProtocolSerializer protobufProtocolSerializer = new ProtobufProtocolSerializer(); Set<BasicTypes.Entry> putEntries = new HashSet<>(); putEntries.add(ProtobufUtilities.createEntry(serializationService, 2.2f, TEST_MULTIOP_VALUE1)); putEntries.add(ProtobufUtilities.createEntry(serializationService, TEST_MULTIOP_KEY2, @@ -224,7 +226,6 @@ public class CacheOperationsJUnitTest { @Test public void testResponseToGetWithNoData() throws Exception { // Get request without any data set must return a null - ProtobufProtocolSerializer protobufProtocolSerializer = new ProtobufProtocolSerializer(); ClientProtocol.Message getMessage = MessageUtil.makeGetRequestMessage(serializationService, TEST_KEY, TEST_REGION); protobufProtocolSerializer.serialize(getMessage, outputStream); @@ -238,7 +239,6 @@ public class CacheOperationsJUnitTest { @Test public void testNewProtocolGetRegionNamesCallSucceeds() throws Exception { - ProtobufProtocolSerializer protobufProtocolSerializer = new ProtobufProtocolSerializer(); RegionAPI.GetRegionNamesRequest getRegionNamesRequest = ProtobufRequestUtilities.createGetRegionNamesRequest(); @@ -252,7 +252,6 @@ public class CacheOperationsJUnitTest { public void testNewProtocolGetSizeCall() throws Exception { System.setProperty("geode.feature-protobuf-protocol", "true"); - ProtobufProtocolSerializer protobufProtocolSerializer = new ProtobufProtocolSerializer(); ClientProtocol.Message putMessage = ProtobufRequestUtilities.createPutRequest(TEST_REGION, ProtobufUtilities.createEntry(serializationService, TEST_KEY, TEST_VALUE)); protobufProtocolSerializer.serialize(putMessage, outputStream); @@ -269,17 +268,6 @@ public class CacheOperationsJUnitTest { assertEquals(1, getSizeResponse.getSize()); } - private void validateGetResponse(Socket socket, - ProtobufProtocolSerializer protobufProtocolSerializer, Object expectedValue) - throws InvalidProtocolMessageException, IOException { - ClientProtocol.Message response = deserializeResponse(socket, protobufProtocolSerializer); - - assertEquals(ClientProtocol.Message.MessageTypeCase.GETRESPONSE, response.getMessageTypeCase()); - RegionAPI.GetResponse getResponse = response.getGetResponse(); - BasicTypes.EncodedValue result = getResponse.getResult(); - assertEquals(BasicTypes.EncodedValue.ValueCase.STRINGRESULT, result.getValueCase()); - assertEquals(expectedValue, result.getStringResult()); - } private ClientProtocol.Message deserializeResponse(Socket socket, ProtobufProtocolSerializer protobufProtocolSerializer) diff --git a/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/operations/PutIfAbsentRequestIntegrationTest.java b/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/operations/PutIfAbsentRequestIntegrationTest.java new file mode 100644 index 0000000..008e495 --- /dev/null +++ b/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/operations/PutIfAbsentRequestIntegrationTest.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.internal.protocol.protobuf.v1.operations; + +import static org.apache.geode.internal.protocol.protobuf.v1.MessageUtil.validateGetResponse; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import org.awaitility.Awaitility; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; +import org.junit.experimental.categories.Category; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.CacheFactory; +import org.apache.geode.cache.DataPolicy; +import org.apache.geode.cache.RegionFactory; +import org.apache.geode.cache.server.CacheServer; +import org.apache.geode.distributed.ConfigurationProperties; +import org.apache.geode.internal.AvailablePortHelper; +import org.apache.geode.internal.net.SocketCreatorFactory; +import org.apache.geode.internal.protocol.protobuf.v1.BasicTypes; +import org.apache.geode.internal.protocol.protobuf.v1.ClientProtocol; +import org.apache.geode.internal.protocol.protobuf.v1.MessageUtil; +import org.apache.geode.internal.protocol.protobuf.v1.ProtobufRequestUtilities; +import org.apache.geode.internal.protocol.protobuf.v1.ProtobufSerializationService; +import org.apache.geode.internal.protocol.protobuf.v1.RegionAPI; +import org.apache.geode.internal.protocol.protobuf.v1.serializer.ProtobufProtocolSerializer; +import org.apache.geode.internal.protocol.protobuf.v1.serializer.exception.InvalidProtocolMessageException; +import org.apache.geode.test.junit.categories.IntegrationTest; + +@Category(IntegrationTest.class) +public class PutIfAbsentRequestIntegrationTest { + private static final String TEST_REGION = "testRegion"; + private static final Object TEST_KEY = "testKey"; + private Cache cache; + private Socket socket; + private OutputStream outputStream; + private ProtobufSerializationService serializationService; + private ProtobufProtocolSerializer protobufProtocolSerializer; + + @Rule + public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + private InputStream inputStream; + + private void doSetup(DataPolicy dataPolicy) throws IOException { + System.setProperty("geode.feature-protobuf-protocol", "true"); + + CacheFactory cacheFactory = new CacheFactory(new Properties()); + cacheFactory.set(ConfigurationProperties.MCAST_PORT, "0"); + cacheFactory.set(ConfigurationProperties.ENABLE_CLUSTER_CONFIGURATION, "false"); + cacheFactory.set(ConfigurationProperties.USE_CLUSTER_CONFIGURATION, "false"); + cache = cacheFactory.create(); + + CacheServer cacheServer = cache.addCacheServer(); + final int cacheServerPort = AvailablePortHelper.getRandomAvailableTCPPort(); + cacheServer.setPort(cacheServerPort); + cacheServer.start(); + + RegionFactory<Object, Object> regionFactory = cache.createRegionFactory(); + regionFactory.setDataPolicy(dataPolicy); + regionFactory.create(TEST_REGION); + + + socket = new Socket("localhost", cacheServerPort); + Awaitility.await().atMost(5, TimeUnit.SECONDS).until(socket::isConnected); + outputStream = socket.getOutputStream(); + inputStream = socket.getInputStream(); + + MessageUtil.performAndVerifyHandshake(socket); + + serializationService = new ProtobufSerializationService(); + protobufProtocolSerializer = new ProtobufProtocolSerializer(); + } + + @After + public void cleanUp() throws IOException { + cache.close(); + socket.close(); + SocketCreatorFactory.close(); + } + + @Test + public void testPutIfAbsentRequest() throws Exception { + doSetup(DataPolicy.REPLICATE); + + final BasicTypes.EncodedValue encodedKey = serializationService.encode(TEST_KEY); + final String testValue = "testValue"; + final String testValue2 = "testValue2"; + final BasicTypes.Entry entry1 = BasicTypes.Entry.newBuilder().setKey(encodedKey) + .setValue(serializationService.encode(testValue)).build(); + assertNull(serializationService.decode(doPutIfAbsent(entry1).getOldValue())); + + protobufProtocolSerializer.serialize( + ProtobufRequestUtilities.createGetRequest(TEST_REGION, encodedKey), + socket.getOutputStream()); + validateGetResponse(socket, protobufProtocolSerializer, testValue); + + final BasicTypes.Entry entry2 = BasicTypes.Entry.newBuilder().setKey(encodedKey) + .setValue(serializationService.encode(testValue2)).build(); + + // same value still present + assertEquals(testValue, serializationService.decode(doPutIfAbsent(entry2).getOldValue())); + protobufProtocolSerializer.serialize( + ProtobufRequestUtilities.createGetRequest(TEST_REGION, encodedKey), + socket.getOutputStream()); + validateGetResponse(socket, protobufProtocolSerializer, testValue); + } + + /** + * This should fail because DataPolicy.NORMAL doesn't allow concurrent cache ops. + */ + @Test + public void testPutIfAbsentRequestOnDataPolicyNormal() throws Exception { + doSetup(DataPolicy.NORMAL); + + final BasicTypes.EncodedValue encodedKey = serializationService.encode(TEST_KEY); + final String testValue = "testValue"; + final BasicTypes.EncodedValue encodedValue = serializationService.encode(testValue); + final BasicTypes.Entry entry = + BasicTypes.Entry.newBuilder().setKey(encodedKey).setValue(encodedValue).build(); + ProtobufRequestUtilities.createPutIfAbsentRequest(TEST_REGION, entry) + .writeDelimitedTo(outputStream); + + final ClientProtocol.Message response = ClientProtocol.Message.parseDelimitedFrom(inputStream); + + assertEquals(ClientProtocol.Message.MessageTypeCase.ERRORRESPONSE, + response.getMessageTypeCase()); + assertEquals(BasicTypes.ErrorCode.UNSUPPORTED_OPERATION, + response.getErrorResponse().getError().getErrorCode()); + } + + private RegionAPI.PutIfAbsentResponse doPutIfAbsent(BasicTypes.Entry entry) + throws IOException, InvalidProtocolMessageException { + final ClientProtocol.Message putIfAbsentRequest = + ProtobufRequestUtilities.createPutIfAbsentRequest(TEST_REGION, entry); + + protobufProtocolSerializer.serialize(putIfAbsentRequest, outputStream); + ClientProtocol.Message response = protobufProtocolSerializer.deserialize(inputStream); + + assertEquals(ClientProtocol.Message.MessageTypeCase.PUTIFABSENTRESPONSE, + response.getMessageTypeCase()); + return response.getPutIfAbsentResponse(); + } + + +} diff --git a/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/operations/PutIfAbsentRequestOperationHandlerJUnitTest.java b/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/operations/PutIfAbsentRequestOperationHandlerJUnitTest.java new file mode 100644 index 0000000..b169eeb --- /dev/null +++ b/geode-protobuf/src/test/java/org/apache/geode/internal/protocol/protobuf/v1/operations/PutIfAbsentRequestOperationHandlerJUnitTest.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.internal.protocol.protobuf.v1.operations; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import org.apache.geode.cache.Region; +import org.apache.geode.internal.protocol.TestExecutionContext; +import org.apache.geode.internal.protocol.protobuf.v1.BasicTypes; +import org.apache.geode.internal.protocol.protobuf.v1.RegionAPI; +import org.apache.geode.internal.protocol.protobuf.v1.Result; +import org.apache.geode.internal.protocol.protobuf.v1.serialization.exception.DecodingException; +import org.apache.geode.internal.protocol.protobuf.v1.serialization.exception.EncodingException; +import org.apache.geode.internal.protocol.protobuf.v1.utilities.ProtobufUtilities; +import org.apache.geode.test.junit.categories.UnitTest; + +@Category(UnitTest.class) +@SuppressWarnings("unchecked") // Region lacks generics when we look it up +public class PutIfAbsentRequestOperationHandlerJUnitTest extends OperationHandlerJUnitTest { + private final String TEST_KEY = "my key"; + private final String TEST_VALUE = "99"; + private final String TEST_REGION = "test region"; + private Region regionMock; + private PutIfAbsentRequestOperationHandler operationHandler; + + @Before + public void setUp() throws Exception { + regionMock = mock(Region.class); + operationHandler = new PutIfAbsentRequestOperationHandler(); + when(cacheStub.getRegion(TEST_REGION)).thenReturn(regionMock); + } + + @Test + public void newEntrySucceeds() throws Exception { + when(regionMock.putIfAbsent(TEST_KEY, TEST_VALUE)).thenReturn(null); + + Result<RegionAPI.PutIfAbsentResponse> result1 = operationHandler.process(serializationService, + generateTestRequest(), TestExecutionContext.getNoAuthCacheExecutionContext(cacheStub)); + + assertNull(serializationService.decode(result1.getMessage().getOldValue())); + + verify(regionMock).putIfAbsent(TEST_KEY, TEST_VALUE); + verify(regionMock, times(1)).putIfAbsent(any(), any()); + } + + @Test + public void existingEntryFails() throws Exception { + when(regionMock.putIfAbsent(TEST_KEY, TEST_VALUE)).thenReturn(1); + + Result<RegionAPI.PutIfAbsentResponse> result1 = operationHandler.process(serializationService, + generateTestRequest(), TestExecutionContext.getNoAuthCacheExecutionContext(cacheStub)); + + assertNotNull(serializationService.decode(result1.getMessage().getOldValue())); + + verify(regionMock).putIfAbsent(TEST_KEY, TEST_VALUE); + verify(regionMock, times(1)).putIfAbsent(any(), any()); + } + + @Test + public void failsWithNoAuthCacheExecutionContext() throws Exception { + Result<RegionAPI.PutIfAbsentResponse> result1 = operationHandler.process(serializationService, + RegionAPI.PutIfAbsentRequest.newBuilder().build(), + TestExecutionContext.getNoAuthCacheExecutionContext(cacheStub)); + + assertEquals(BasicTypes.ErrorCode.SERVER_ERROR, + result1.getErrorMessage().getError().getErrorCode()); + } + + @Test(expected = DecodingException.class) + public void unsetEntrythrowsDecodingException() throws Exception { + Result<RegionAPI.PutIfAbsentResponse> result1 = + operationHandler.process(serializationService, generateTestRequest(true, false), + TestExecutionContext.getNoAuthCacheExecutionContext(cacheStub)); + + assertEquals(BasicTypes.ErrorCode.INVALID_REQUEST, + result1.getErrorMessage().getError().getErrorCode()); + } + + @Test + public void unsetRegionGetsServerError() throws Exception { + Result<RegionAPI.PutIfAbsentResponse> result1 = + operationHandler.process(serializationService, generateTestRequest(false, true), + TestExecutionContext.getNoAuthCacheExecutionContext(cacheStub)); + + assertEquals(BasicTypes.ErrorCode.SERVER_ERROR, + result1.getErrorMessage().getError().getErrorCode()); + } + + @Test + public void nonexistingRegionReturnsServerError() throws Exception { + when(cacheStub.getRegion(TEST_REGION)).thenReturn(null); + + Result<RegionAPI.PutIfAbsentResponse> result1 = operationHandler.process(serializationService, + generateTestRequest(), TestExecutionContext.getNoAuthCacheExecutionContext(cacheStub)); + + assertEquals(BasicTypes.ErrorCode.SERVER_ERROR, + result1.getErrorMessage().getError().getErrorCode()); + } + + /** + * Some regions (DataPolicy.NORMAL, for example) don't support concurrent ops such as putIfAbsent. + */ + @Test(expected = UnsupportedOperationException.class) + public void unsupportedOperation() throws Exception { + when(regionMock.putIfAbsent(any(), any())).thenThrow(new UnsupportedOperationException()); + + Result<RegionAPI.PutIfAbsentResponse> result1 = operationHandler.process(serializationService, + generateTestRequest(), TestExecutionContext.getNoAuthCacheExecutionContext(cacheStub)); + assertEquals(BasicTypes.ErrorCode.INVALID_REQUEST, + result1.getErrorMessage().getError().getErrorCode()); + } + + @Test + public void invalidRegionReturnsInvalidRequestError() throws Exception { + // doesn't test which regions are invalid; those are documented under Cache.getRegion. + when(cacheStub.getRegion(any())).thenThrow(new IllegalArgumentException()); + + Result<RegionAPI.PutIfAbsentResponse> result1 = operationHandler.process(serializationService, + generateTestRequest(), TestExecutionContext.getNoAuthCacheExecutionContext(cacheStub)); + assertEquals(BasicTypes.ErrorCode.INVALID_REQUEST, + result1.getErrorMessage().getError().getErrorCode()); + } + + private RegionAPI.PutIfAbsentRequest generateTestRequest(boolean includeRegion, + boolean includeEntry) throws EncodingException { + RegionAPI.PutIfAbsentRequest.Builder builder = RegionAPI.PutIfAbsentRequest.newBuilder(); + + if (includeRegion) { + builder.setRegionName(TEST_REGION); + } + + if (includeEntry) { + BasicTypes.EncodedValue testKey = serializationService.encode(TEST_KEY); + BasicTypes.EncodedValue testValue = serializationService.encode(TEST_VALUE); + BasicTypes.Entry testEntry = ProtobufUtilities.createEntry(testKey, testValue); + builder.setEntry(testEntry); + } + + return builder.build(); + } + + private RegionAPI.PutIfAbsentRequest generateTestRequest() throws EncodingException { + return generateTestRequest(true, true); + } + +} -- To stop receiving notification emails like this one, please contact gosulli...@apache.org.