This is an automated email from the ASF dual-hosted git repository. upthewaterspout pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/geode.git
commit 2f596638c68472ccacb0085d8982b574ca645cc4 Author: Sarge <[email protected]> AuthorDate: Wed Dec 6 16:19:37 2017 -0800 GEODE-4054: Create module for Protobuf message-based client --- geode-experimental-driver/build.gradle | 28 +++ .../apache/geode/experimental/driver/Driver.java | 50 +++++ .../geode/experimental/driver/DriverFactory.java | 60 ++++++ .../geode/experimental/driver/ProtobufDriver.java | 178 ++++++++++++++++++ .../geode/experimental/driver/ProtobufRegion.java | 206 +++++++++++++++++++++ .../apache/geode/experimental/driver/Region.java | 86 +++++++++ .../experimental/driver/RegionAttributes.java | 79 ++++++++ .../geode/experimental/driver/ValueEncoder.java | 112 +++++++++++ .../experimental/driver/RegionIntegrationTest.java | 102 ++++++++++ .../experimental/driver/ValueEncoderTest.java | 50 +++++ settings.gradle | 1 + 11 files changed, 952 insertions(+) diff --git a/geode-experimental-driver/build.gradle b/geode-experimental-driver/build.gradle new file mode 100644 index 0000000..ce3eb46 --- /dev/null +++ b/geode-experimental-driver/build.gradle @@ -0,0 +1,28 @@ +/* + * 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. + */ + +dependencies { + compile project(':geode-common') + compile project(':geode-protobuf-messages') + + compile 'com.google.protobuf:protobuf-java:' + project.'protobuf-java.version' + testCompile project(':geode-core') + testCompile project(':geode-junit') + testCompile project(':geode-protobuf') + testCompile project(':geode-client-protocol') + testCompile files(project(':geode-core').sourceSets.test.output) +} diff --git a/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/Driver.java b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/Driver.java new file mode 100644 index 0000000..59e9f82 --- /dev/null +++ b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/Driver.java @@ -0,0 +1,50 @@ +/* + * 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.experimental.driver; + +import java.io.IOException; +import java.util.Set; + +import org.apache.geode.annotations.Experimental; + +/** + * Defines the behaviors of a driver for communicating with a GemFire server by way of the new + * protocol. + * + * <strong>This code is an experimental prototype and is presented "as is" with no warranty, + * suitability, or fitness of purpose implied.</strong> + */ +@Experimental +public interface Driver { + /** + * Retrieves a set of unique names of regions in the GemFire server to which this driver is + * connected. + * + * @return Set of strings of names that uniquely identify regions. + * @throws IOException + */ + Set<String> getRegionNames() throws IOException; + + /** + * Creates an implementation of the region interface for the region with the unique name of + * <code>regionName</code>. + * + * @param regionName String that uniquely identifies the region. + * @param <K> Type of region keys. + * @param <V> Type of region values. + * @return + */ + <K, V> Region<K, V> getRegion(String regionName); +} diff --git a/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/DriverFactory.java b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/DriverFactory.java new file mode 100644 index 0000000..267bb3d --- /dev/null +++ b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/DriverFactory.java @@ -0,0 +1,60 @@ +/* + * 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.experimental.driver; + +import java.net.InetSocketAddress; +import java.util.HashSet; +import java.util.Set; + +import org.apache.geode.annotations.Experimental; + +/** + * This is an experimental driver for connecting a client to a geode cluster. This driver is still + * under development. For a working, full featured client, use ClientCache in geode-core. This + * driver factory supports the builder style of chaining together mutators. + * + * <strong>This code is an experimental prototype and is presented "as is" with no warranty, + * suitability, or fitness of purpose implied.</strong> + */ +@Experimental +public class DriverFactory { + /** + * Set of Internet-address-or-host-name/port pairs of the locators to use to find GemFire servers + * that have Protobuf enabled. + */ + private Set<InetSocketAddress> locators = new HashSet<InetSocketAddress>(); + + /** + * Adds a locator at <code>host</code> and <code>port</code> to the set of locators to use. + * + * @param host Internet address or host name. + * @param port Port number. + * @return This driver factory. + */ + public DriverFactory addLocator(String host, int port) { + this.locators.add(new InetSocketAddress(host, port)); + return this; + } + + /** + * Creates a driver configured to use all the locators about which this driver factory knows. + * + * @return New driver. + * @throws Exception + */ + public Driver create() throws Exception { + return new ProtobufDriver(locators); + } +} diff --git a/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/ProtobufDriver.java b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/ProtobufDriver.java new file mode 100644 index 0000000..1dac79f --- /dev/null +++ b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/ProtobufDriver.java @@ -0,0 +1,178 @@ +/* + * 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.experimental.driver; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.apache.geode.annotations.Experimental; +import org.apache.geode.internal.protocol.protobuf.ProtocolVersion; +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; + +/** + * Implements the behaviors of a driver for communicating with a GemFire server by way of the new + * protocol. + * + * <strong>This code is an experimental prototype and is presented "as is" with no warranty, + * suitability, or fitness of purpose implied.</strong> + */ +@Experimental +public class ProtobufDriver implements Driver { + /** + * Set of Internet-address-or-host-name/port pairs of the locators to use to find GemFire servers + * that have Protobuf enabled. + */ + private final Set<InetSocketAddress> locators; + + /** + * Socket to a GemFire locator that has Protobuf enabled. + */ + private final Socket socket; + + /** + * Creates a driver implementation that communicates via <code>socket</code> to a GemFire locator. + * + * @param locators Set of Internet-address-or-host-name/port pairs of the locators to use to find + * GemFire servers that have Protobuf enabled. + * @throws IOException + */ + ProtobufDriver(Set<InetSocketAddress> locators) throws IOException { + this.locators = locators; + Collection<InetSocketAddress> servers = getAvailableServers(); + InetSocketAddress anyServer = servers.iterator().next(); + socket = new Socket(anyServer.getAddress(), anyServer.getPort()); + + final OutputStream outputStream = socket.getOutputStream(); + ProtocolVersion.NewConnectionClientVersion.newBuilder() + .setMajorVersion(ProtocolVersion.MajorVersions.CURRENT_MAJOR_VERSION_VALUE) + .setMinorVersion(ProtocolVersion.MinorVersions.CURRENT_MINOR_VERSION_VALUE).build() + .writeDelimitedTo(outputStream); + + final InputStream inputStream = socket.getInputStream(); + if (!ProtocolVersion.VersionAcknowledgement.parseDelimitedFrom(inputStream) + .getVersionAccepted()) { + throw new IOException("Failed protocol version verification."); + } + } + + /** + * Retrieves a set of unique names of regions in the GemFire server to which this driver is + * connected. + * + * @return Set of strings of names that uniquely identify regions. + * @throws IOException + */ + @Override + public Set<String> getRegionNames() throws IOException { + Set<String> regionNames = new HashSet<>(); + + final OutputStream outputStream = socket.getOutputStream(); + ClientProtocol.Message.newBuilder() + .setRequest(ClientProtocol.Request.newBuilder() + .setGetRegionNamesRequest(RegionAPI.GetRegionNamesRequest.newBuilder())) + .build().writeDelimitedTo(outputStream); + + final InputStream inputStream = socket.getInputStream(); + final RegionAPI.GetRegionNamesResponse getRegionNamesResponse = ClientProtocol.Message + .parseDelimitedFrom(inputStream).getResponse().getGetRegionNamesResponse(); + for (int i = 0; i < getRegionNamesResponse.getRegionsCount(); ++i) { + regionNames.add(getRegionNamesResponse.getRegions(i)); + } + + return regionNames; + } + + /** + * Creates an implementation of the region interface for the region with the unique name of + * <code>regionName</code>. + * + * @param regionName String that uniquely identifies the region. + * @param <K> Type of region keys. + * @param <V> Type of region values. + * @return + */ + @Override + public <K, V> Region<K, V> getRegion(String regionName) { + return new ProtobufRegion(regionName, socket); + } + + /** + * Queries a locator for the GemFire servers that have Protobuf enabled. + * + * @return Set of Internet-address-or-host-name/port pairs of the GemFire servers that have + * Protobuf enabled. + * @throws IOException + */ + private Collection<InetSocketAddress> getAvailableServers() throws IOException { + IOException lastException = null; + + for (InetSocketAddress locator : locators) { + try { + final Socket locatorSocket = new Socket(locator.getAddress(), locator.getPort()); + + final OutputStream outputStream = locatorSocket.getOutputStream(); + ProtocolVersion.NewConnectionClientVersion.newBuilder() + .setMajorVersion(ProtocolVersion.MajorVersions.CURRENT_MAJOR_VERSION_VALUE) + .setMinorVersion(ProtocolVersion.MinorVersions.CURRENT_MINOR_VERSION_VALUE).build() + .writeDelimitedTo(outputStream); + + // The locator does not currently send a reply to the ProtocolVersion... + // if + // (!ProtocolVersion.HandshakeAcknowledgement.parseDelimitedFrom(inputStream).getHandshakePassed()) + // { + // throw new IOException("Failed ProtocolVersion."); + // } + + ClientProtocol.Message.newBuilder() + .setRequest(ClientProtocol.Request.newBuilder() + .setGetAvailableServersRequest(LocatorAPI.GetAvailableServersRequest.newBuilder())) + .build().writeDelimitedTo(outputStream); + + final InputStream inputStream = locatorSocket.getInputStream(); + LocatorAPI.GetAvailableServersResponse getAvailableServersResponse = ClientProtocol.Message + .parseDelimitedFrom(inputStream).getResponse().getGetAvailableServersResponse(); + if (getAvailableServersResponse.getServersCount() < 1) { + continue; + } + + ArrayList<InetSocketAddress> availableServers = + new ArrayList<>(getAvailableServersResponse.getServersCount()); + for (int i = 0; i < getAvailableServersResponse.getServersCount(); ++i) { + final BasicTypes.Server server = getAvailableServersResponse.getServers(i); + availableServers.add(new InetSocketAddress(server.getHostname(), server.getPort())); + } + return availableServers; + } catch (IOException e) { + lastException = e; + } + } + + if (lastException != null) { + throw lastException; + } else { + throw new IllegalStateException("No locators"); + } + } +} 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 new file mode 100644 index 0000000..5ff4f1a --- /dev/null +++ b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/ProtobufRegion.java @@ -0,0 +1,206 @@ +/* + * 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.experimental.driver; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +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.RegionAPI; + +/** + * Implements the behaviors of a GemFire region. Send and receives Protobuf messages on the provided + * socket to communicate with a GemFire server that has Protobuf enabled. + * + * <strong>This code is an experimental prototype and is presented "as is" with no warranty, + * suitability, or fitness of purpose implied.</strong> + * + * @param <K> Type of region keys. + * @param <V> Type of region values. + */ +@Experimental +public class ProtobufRegion<K, V> implements Region<K, V> { + /** + * String that uniquely identifies the region. + */ + final String name; + + /** + * Socket to a GemFire server that has Protobuf enabled. + */ + final Socket socket; + + /** + * Creates a region implementation for the region <code>name</code> that communicates via + * <code>socket</code> to a GemFire server. + * + * @param name String that uniquely identifies the region. + * @param socket Socket to a GemFire server that has Protobuf enabled. + */ + ProtobufRegion(String name, Socket socket) { + this.name = name; + this.socket = socket; + } + + /** + * Captures a snapshot of the attributes (e.g., size) of this region. + * + * @return Attributes associated with this region. + * @throws IOException + */ + @Override + public RegionAttributes getRegionAttributes() throws IOException { + final OutputStream outputStream = socket.getOutputStream(); + ClientProtocol.Message.newBuilder() + .setRequest(ClientProtocol.Request.newBuilder() + .setGetRegionRequest(RegionAPI.GetRegionRequest.newBuilder().setRegionName(name))) + .build().writeDelimitedTo(outputStream); + + final InputStream inputStream = socket.getInputStream(); + return new RegionAttributes(ClientProtocol.Message.parseDelimitedFrom(inputStream).getResponse() + .getGetRegionResponse().getRegion()); + } + + /** + * Gets the value, if any, contained in this region for the <code>key</code>. + * + * @param key Unique key associated with a value. + * @return Value, if any, associated with <code>key</code>. + * @throws IOException + */ + @Override + public V get(K key) throws IOException { + final OutputStream outputStream = socket.getOutputStream(); + ClientProtocol.Message.newBuilder() + .setRequest(ClientProtocol.Request.newBuilder().setGetRequest(RegionAPI.GetRequest + .newBuilder().setRegionName(name).setKey(ValueEncoder.encodeValue(key)))) + .build().writeDelimitedTo(outputStream); + + final InputStream inputStream = socket.getInputStream(); + return (V) ValueEncoder.decodeValue(ClientProtocol.Message.parseDelimitedFrom(inputStream) + .getResponse().getGetResponse().getResult()); + } + + /** + * Gets the values, if any, contained in this region for the collection of <code>keys</code>. + * + * @param keys Collection of unique keys associated with values. + * @return Map from <code>keys</code> to their associated values. + * @throws IOException + */ + @Override + public Map<K, V> getAll(Collection<K> keys) throws IOException { + Map<K, V> values = new HashMap<>(); + + final OutputStream outputStream = socket.getOutputStream(); + RegionAPI.GetAllRequest.Builder getAllRequest = RegionAPI.GetAllRequest.newBuilder(); + getAllRequest.setRegionName(name); + for (K key : keys) { + getAllRequest.addKey(ValueEncoder.encodeValue(key.toString())); + } + ClientProtocol.Message.newBuilder() + .setRequest(ClientProtocol.Request.newBuilder().setGetAllRequest(getAllRequest)).build() + .writeDelimitedTo(outputStream); + + final InputStream inputStream = socket.getInputStream(); + final RegionAPI.GetAllResponse getAllResponse = + ClientProtocol.Message.parseDelimitedFrom(inputStream).getResponse().getGetAllResponse(); + for (BasicTypes.Entry entry : getAllResponse.getEntriesList()) { + values.put((K) ValueEncoder.decodeValue(entry.getKey()), + (V) ValueEncoder.decodeValue(entry.getValue())); + } + + return values; + } + + /** + * Puts the <code>value</code> into this region for the <code>key</code>. + * + * @param key Unique key to associate with the <code>value</code>. + * @param value Value to associate with the <code>key</code>. + * @throws IOException + */ + @Override + public void put(K key, V value) throws IOException { + final OutputStream outputStream = socket.getOutputStream(); + ClientProtocol.Message.newBuilder() + .setRequest(ClientProtocol.Request.newBuilder() + .setPutRequest(RegionAPI.PutRequest.newBuilder().setRegionName(name) + .setEntry(ValueEncoder.encodeEntry(key, value)))) + .build().writeDelimitedTo(outputStream); + + final InputStream inputStream = socket.getInputStream(); + ClientProtocol.Message.parseDelimitedFrom(inputStream).getResponse().getPutResponse(); + } + + /** + * Puts the map from keys to <code>values</code> into this region. If any one key/value pair can + * not be inserted, the remaining pair insertions will be attempted. + * + * @param values Map from <code>keys</code> to their associated values. + * @throws IOException + */ + @Override + public void putAll(Map<K, V> values) throws IOException { + final OutputStream outputStream = socket.getOutputStream(); + RegionAPI.PutAllRequest.Builder putAllRequest = RegionAPI.PutAllRequest.newBuilder(); + putAllRequest.setRegionName(name); + for (K key : values.keySet()) { + putAllRequest.addEntry(ValueEncoder.encodeEntry(key, values.get(key))); + } + ClientProtocol.Message.newBuilder() + .setRequest(ClientProtocol.Request.newBuilder().setPutAllRequest(putAllRequest)).build() + .writeDelimitedTo(outputStream); + + final InputStream inputStream = socket.getInputStream(); + final RegionAPI.PutAllResponse putAllResponse = + ClientProtocol.Message.parseDelimitedFrom(inputStream).getResponse().getPutAllResponse(); + if (0 < putAllResponse.getFailedKeysCount()) { + StringBuilder builder = new StringBuilder(); + for (BasicTypes.KeyedError keyedError : putAllResponse.getFailedKeysList()) { + if (0 < builder.length()) { + builder.append(", "); + } + builder.append(ValueEncoder.decodeValue(keyedError.getKey()).toString()); + } + throw new IOException("Unable to put the following keys: " + builder.toString()); + } + } + + /** + * Removes any value associated with the <code>key</code> from this region. + * + * @param key Unique key associated with a value. + * @throws IOException + */ + @Override + public void remove(K key) throws IOException { + final OutputStream outputStream = socket.getOutputStream(); + ClientProtocol.Message.newBuilder() + .setRequest(ClientProtocol.Request.newBuilder().setRemoveRequest(RegionAPI.RemoveRequest + .newBuilder().setRegionName(name).setKey(ValueEncoder.encodeValue(key)))) + .build().writeDelimitedTo(outputStream); + + final InputStream inputStream = socket.getInputStream(); + ClientProtocol.Message.parseDelimitedFrom(inputStream).getResponse().getRemoveResponse(); + } +} 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 new file mode 100644 index 0000000..75c2a94 --- /dev/null +++ b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/Region.java @@ -0,0 +1,86 @@ +/* + * 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.experimental.driver; + +import java.io.IOException; +import java.util.Collection; +import java.util.Map; + +import org.apache.geode.annotations.Experimental; + +/** + * Defines the behaviors of a GemFire region. A region is an associative array from unique keys to + * values. For each key, the region will contain zero or one value. + * + * <strong>This code is an experimental prototype and is presented "as is" with no warranty, + * suitability, or fitness of purpose implied.</strong> + * + * @param <K> Type of region keys. + * @param <V> Type of region values. + */ +@Experimental +public interface Region<K, V> { + /** + * Captures a snapshot of the attributes (e.g., size) of this region. + * + * @return Attributes associated with this region. + * @throws IOException + */ + RegionAttributes getRegionAttributes() throws IOException; + + /** + * Gets the value, if any, contained in this region for the <code>key</code>. + * + * @param key Unique key associated with a value. + * @return Value, if any, associated with <code>key</code>. + * @throws IOException + */ + V get(K key) throws IOException; + + /** + * Gets the values, if any, contained in this region for the collection of <code>keys</code>. + * + * @param keys Collection of unique keys associated with values. + * @return Map from <code>keys</code> to their associated values. + * @throws IOException + */ + Map<K, V> getAll(Collection<K> keys) throws IOException; + + /** + * Puts the <code>value</code> into this region for the <code>key</code>. + * + * @param key Unique key to associate with the <code>value</code>. + * @param value Value to associate with the <code>key</code>. + * @throws IOException + */ + void put(K key, V value) throws IOException; + + /** + * Puts the map from keys to <code>values</code> into this region. If any one key/value pair can + * not be inserted, the remaining pair insertions will be attempted. + * + * @param values Map from <code>keys</code> to their associated values. + * @throws IOException + */ + void putAll(Map<K, V> values) throws IOException; + + /** + * Removes any value associated with the <code>key</code> from this region. + * + * @param key Unique key associated with a value. + * @throws IOException + */ + void remove(K key) throws IOException; +} diff --git a/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/RegionAttributes.java b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/RegionAttributes.java new file mode 100644 index 0000000..fea1021 --- /dev/null +++ b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/RegionAttributes.java @@ -0,0 +1,79 @@ +/* + * 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.experimental.driver; + +import org.apache.geode.annotations.Experimental; +import org.apache.geode.internal.protocol.protobuf.v1.BasicTypes; + +/** + * Encapsulates the attributes of a region in a GemFire server. + * + * <strong>This code is an experimental prototype and is presented "as is" with no warranty, + * suitability, or fitness of purpose implied.</strong> + */ +@Experimental +public class RegionAttributes { + /** + * String that uniquely identifies the region within a GemFire server. + */ + public String name; + + /** + * Specifies how a local cache will handle the data for the region. + */ + public String dataPolicy; + + /** + * Whether modifications to the region are acknowledged throughout the GemFire distributed system. + */ + public String scope; + + /** + * String that is the fully-qualified class name of the type of which all keys must be instances. + * May be <code>null</code>. + */ + public String keyConstraint; + + /** + * String that is the fully-qualified class name of the type of which all values must be + * instances. May be <code>null</code>. + */ + public String valueConstraint; + + /** + * Whether the region is persisted to disk. + */ + public boolean persisted; + + /** + * Number of key/value pairs in the region. + */ + public long size; + + /** + * Creates an encapsulation of region attributes. + * + * @param region Protobuf encoded region attributes. + */ + public RegionAttributes(BasicTypes.Region region) { + this.name = region.getName(); + this.dataPolicy = region.getDataPolicy(); + this.scope = region.getScope(); + this.keyConstraint = region.getKeyConstraint(); + this.valueConstraint = region.getValueConstraint(); + this.persisted = region.getPersisted(); + this.size = region.getSize(); + } +} diff --git a/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/ValueEncoder.java b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/ValueEncoder.java new file mode 100644 index 0000000..4b99913 --- /dev/null +++ b/geode-experimental-driver/src/main/java/org/apache/geode/experimental/driver/ValueEncoder.java @@ -0,0 +1,112 @@ +/* + * 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.experimental.driver; + +import com.google.protobuf.ByteString; + +import org.apache.geode.annotations.Experimental; +import org.apache.geode.internal.protocol.protobuf.v1.BasicTypes; + +/** + * Encodes and decodes Java objects to and from Protobuf encoded values. + * + * <strong>This code is an experimental prototype and is presented "as is" with no warranty, + * suitability, or fitness of purpose implied.</strong> + */ +@Experimental +class ValueEncoder { + /** + * Encodes a Java object into a Protobuf encoded value. + * + * @param unencodedValue Java object to encode. + * @return Encoded value of the Java object. + */ + static BasicTypes.EncodedValue encodeValue(Object unencodedValue) { + BasicTypes.EncodedValue.Builder builder = BasicTypes.EncodedValue.newBuilder(); + if (Integer.class.equals(unencodedValue.getClass())) { + builder.setIntResult((Integer) unencodedValue); + } else if (Long.class.equals(unencodedValue.getClass())) { + builder.setLongResult((Long) unencodedValue); + } else if (Short.class.equals(unencodedValue.getClass())) { + builder.setShortResult((Short) unencodedValue); + } else if (Byte.class.equals(unencodedValue.getClass())) { + builder.setByteResult((Byte) unencodedValue); + } else if (Double.class.equals(unencodedValue.getClass())) { + builder.setDoubleResult((Double) unencodedValue); + } else if (Float.class.equals(unencodedValue.getClass())) { + builder.setFloatResult((Float) unencodedValue); + } else if (byte[].class.equals(unencodedValue.getClass())) { + builder.setBinaryResult(ByteString.copyFrom((byte[]) unencodedValue)); + } else if (Boolean.class.equals(unencodedValue.getClass())) { + builder.setBooleanResult((Boolean) unencodedValue); + } else if (String.class.equals(unencodedValue.getClass())) { + builder.setStringResult((String) unencodedValue); + } else { + throw new IllegalStateException("We don't know how to handle an object of type " + + unencodedValue.getClass() + ": " + unencodedValue); + } + + return builder.build(); + } + + /** + * Decodes a Protobuf encoded value into a Java object. + * + * @param encodedValue Encoded value to decode. + * @return Decoded Java object. + */ + static Object decodeValue(BasicTypes.EncodedValue encodedValue) { + switch (encodedValue.getValueCase()) { + case BINARYRESULT: + return encodedValue.getBinaryResult().toByteArray(); + case BOOLEANRESULT: + return encodedValue.getBooleanResult(); + case BYTERESULT: + return (byte) encodedValue.getByteResult(); + case DOUBLERESULT: + return encodedValue.getDoubleResult(); + case FLOATRESULT: + return encodedValue.getFloatResult(); + case INTRESULT: + return encodedValue.getIntResult(); + case LONGRESULT: + return encodedValue.getLongResult(); + case SHORTRESULT: + return (short) encodedValue.getShortResult(); + case STRINGRESULT: + return encodedValue.getStringResult(); + case VALUE_NOT_SET: + return null; + default: + throw new IllegalStateException( + "Can't decode a value of type " + encodedValue.getValueCase() + ": " + encodedValue); + } + } + + /** + * Encodes a Java object key and a Java object value into a Protobuf encoded entry. + * + * @param unencodedKey Java object key to encode. + * @param unencodedValue Java object value to encode. + * @return Encoded entry of the Java object key and value. + */ + static BasicTypes.Entry encodeEntry(Object unencodedKey, Object unencodedValue) { + if (unencodedValue == null) { + return BasicTypes.Entry.newBuilder().setKey(encodeValue(unencodedKey)).build(); + } + return BasicTypes.Entry.newBuilder().setKey(encodeValue(unencodedKey)) + .setValue(encodeValue(unencodedValue)).build(); + } +} 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 new file mode 100644 index 0000000..4394ec4 --- /dev/null +++ b/geode-experimental-driver/src/test/java/org/apache/geode/experimental/driver/RegionIntegrationTest.java @@ -0,0 +1,102 @@ +/* + * 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.experimental.driver; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Properties; + +import org.junit.After; +import org.junit.Before; +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.RegionShortcut; +import org.apache.geode.cache.server.CacheServer; +import org.apache.geode.distributed.Locator; +import org.apache.geode.test.junit.categories.IntegrationTest; + +@Category(IntegrationTest.class) +public class RegionIntegrationTest { + @Rule + public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + + public static final String REGION = "region"; + private int locatorPort; + private Locator locator; + private CacheServer server; + private Cache cache; + + @Before + public void createServer() throws IOException { + System.setProperty("geode.feature-protobuf-protocol", "true"); + + // Create a cache + CacheFactory cf = new CacheFactory(); + cache = cf.create(); + + // Start a locator + locator = Locator.startLocatorAndDS(0, null, new Properties()); + locatorPort = locator.getPort(); + + // Start a server + server = cache.addCacheServer(); + server.setPort(0); + server.start(); + + // Create a region + cache.createRegionFactory(RegionShortcut.REPLICATE).create(REGION); + } + + @After + public void cleanup() { + locator.stop(); + cache.close(); + } + + @Test + public void getShouldReturnPutValue() throws Exception { + Driver driver = new DriverFactory().addLocator("localhost", locatorPort).create(); + Region region = driver.getRegion("region"); + + region.put("key", "value"); + assertEquals("value", region.get("key")); + + region.remove("key"); + assertEquals(null, region.get("key")); + } + + @Test + public void putWithIntegerKey() throws Exception { + Driver driver = new DriverFactory().addLocator("localhost", locatorPort).create(); + Region region = driver.getRegion("region"); + region.put(37, 42); + assertEquals(42, region.get(37)); + } + + @Test + public void removeWithIntegerKey() throws Exception { + Driver driver = new DriverFactory().addLocator("localhost", locatorPort).create(); + Region region = driver.getRegion("region"); + region.put(37, 42); + region.remove(37); + assertEquals(null, region.get(37)); + } +} diff --git a/geode-experimental-driver/src/test/java/org/apache/geode/experimental/driver/ValueEncoderTest.java b/geode-experimental-driver/src/test/java/org/apache/geode/experimental/driver/ValueEncoderTest.java new file mode 100644 index 0000000..2afe472 --- /dev/null +++ b/geode-experimental-driver/src/test/java/org/apache/geode/experimental/driver/ValueEncoderTest.java @@ -0,0 +1,50 @@ +/* + * 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.experimental.driver; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.io.UnsupportedEncodingException; + +import com.google.protobuf.ByteString; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import org.apache.geode.internal.protocol.protobuf.v1.BasicTypes; +import org.apache.geode.test.junit.categories.UnitTest; + +@Category(UnitTest.class) +public class ValueEncoderTest { + @Test + public void encodeAndDecode() throws Exception { + final Object[] objects = {37, (short) 37, (byte) 37, 37L, 37., 37.F, true, "hello, world"}; + for (Object object : objects) { + assertEquals(object, ValueEncoder.decodeValue(ValueEncoder.encodeValue(object))); + } + + final byte[] bytes = new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}; + assertArrayEquals(bytes, (byte[]) ValueEncoder.decodeValue(ValueEncoder.encodeValue(bytes))); + } + + @Test(expected = IllegalStateException.class) + public void cantDecodeJson() throws UnsupportedEncodingException { + BasicTypes.EncodedValue.Builder builder = BasicTypes.EncodedValue.newBuilder(); + BasicTypes.CustomEncodedValue.Builder customEncodedValue = + BasicTypes.CustomEncodedValue.newBuilder().setValue(ByteString.copyFrom("hello", "UTF-8")); + builder.setCustomEncodedValue(customEncodedValue); + ValueEncoder.decodeValue(builder.build()); + } +} diff --git a/settings.gradle b/settings.gradle index a319689..c9a4991 100644 --- a/settings.gradle +++ b/settings.gradle @@ -39,6 +39,7 @@ include 'extensions/geode-modules-session-internal' include 'extensions/geode-modules-session' include 'extensions/geode-modules-assembly' include 'geode-protobuf' +include 'geode-experimental-driver' include 'geode-protobuf-messages' include 'extensions/session-testing-war' include 'geode-concurrency-test' -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
