From 708c3aa65c70ee48a5051642c77b700c25a03b99 Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Wed, 19 Apr 2017 14:00:55 -0400 Subject: [PATCH] [FAB-3220] sync compositekey api w/ go ChaincodeStub: - getCompositeKey() - splitCompositKey() CompositeKey: - new class - new unit tests Change-Id: I697b7bb02b61a4b8acb5a086580e0ae34405a8c1 Signed-off-by: Luis Sanchez --- core/chaincode/shim/java/build.gradle | 2 + .../fabric/shim/ChaincodeStub.java | 21 +++- .../fabric/shim/ChaincodeStubImpl.java | 19 ++-- .../fabric/shim/ledger/CompositeKey.java | 87 +++++++++++++++ .../ledger/CompositeKeyFormatException.java | 40 +++++++ .../fabric/shim/ledger/CompositeKeyTest.java | 104 ++++++++++++++++++ 6 files changed, 260 insertions(+), 13 deletions(-) create mode 100644 core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ledger/CompositeKey.java create mode 100644 core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ledger/CompositeKeyFormatException.java create mode 100644 core/chaincode/shim/java/src/test/java/org/hyperledger/fabric/shim/ledger/CompositeKeyTest.java diff --git a/core/chaincode/shim/java/build.gradle b/core/chaincode/shim/java/build.gradle index 5b06613f7f5..60084e30804 100644 --- a/core/chaincode/shim/java/build.gradle +++ b/core/chaincode/shim/java/build.gradle @@ -145,6 +145,8 @@ dependencies { compile 'io.grpc:grpc-all:0.13.2' compile 'commons-cli:commons-cli:1.3.1' compile 'io.netty:netty-tcnative-boringssl-static:1.1.33.Fork21:' + tcnative_classifier + testCompile 'junit:junit:4.12' + testCompile 'org.hamcrest:hamcrest-library:1.3' } publishing { diff --git a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ChaincodeStub.java b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ChaincodeStub.java index 645790e74cc..ca15b4439fd 100644 --- a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ChaincodeStub.java +++ b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ChaincodeStub.java @@ -23,6 +23,7 @@ import org.hyperledger.fabric.protos.peer.ChaincodeEventPackage.ChaincodeEvent; import org.hyperledger.fabric.protos.peer.ProposalResponsePackage.Response; +import org.hyperledger.fabric.shim.ledger.CompositeKey; public interface ChaincodeStub { @@ -43,7 +44,7 @@ public interface ChaincodeStub { * @return a list of arguments cast to UTF-8 strings */ List getStringArgs(); - + /** * A convenience method that returns the first argument of the chaincode * invocation for use as a function name. @@ -53,7 +54,7 @@ public interface ChaincodeStub { * @return the function name */ String getFunction(); - + /** * A convenience method that returns all except the first argument of the * chaincode invocation for use as the parameters to the function returned @@ -117,14 +118,22 @@ public interface ChaincodeStub { * * @param objectType * @param attributes - * @return + * @return a composite key */ - String createCompositeKey(String objectType, String[] attributes); + CompositeKey createCompositeKey(String objectType, String... attributes); + + /** + * Parses a composite key from a string. + * + * @param compositeKey a composite key string + * @return a composite key + */ + CompositeKey splitCompositeKey(String compositeKey); /** * Defines the CHAINCODE type event that will be posted to interested * clients when the chaincode's result is committed to the ledger. - * + * * @param name * Name of event. Cannot be null or empty string. * @param payload @@ -220,4 +229,4 @@ default void putStringState(String key, String value) { */ ChaincodeEvent getEvent(); -} \ No newline at end of file +} diff --git a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ChaincodeStubImpl.java b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ChaincodeStubImpl.java index 4fc390df4ae..bcd8e868717 100644 --- a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ChaincodeStubImpl.java +++ b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ChaincodeStubImpl.java @@ -26,6 +26,7 @@ import org.apache.commons.logging.LogFactory; import org.hyperledger.fabric.protos.peer.ChaincodeEventPackage.ChaincodeEvent; import org.hyperledger.fabric.protos.peer.ProposalResponsePackage.Response; +import org.hyperledger.fabric.shim.ledger.CompositeKey; import com.google.protobuf.ByteString; @@ -133,17 +134,21 @@ public void putState(String key, byte[] value) { public void delState(String key) { handler.handleDeleteState(key, txid); } + /* (non-Javadoc) * @see org.hyperledger.fabric.shim.ChaincodeStub#createCompositeKey(java.lang.String, java.lang.String[]) */ @Override - public String createCompositeKey(String objectType, String[] attributes) { - String compositeKey = new String(); - compositeKey = compositeKey + objectType; - for (String attribute : attributes) { - compositeKey = compositeKey + attribute.length() + attribute; - } - return compositeKey; + public CompositeKey createCompositeKey(String objectType, String... attributes) { + return new CompositeKey(objectType, attributes); + } + + /* (non-Javadoc) + * @see org.hyperledger.fabric.shim.ChaincodeStub#splitCompositeKey(java.lang.String) + */ + @Override + public CompositeKey splitCompositeKey(String compositeKey) { + return CompositeKey.parseCompositeKey(compositeKey); } /* (non-Javadoc) diff --git a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ledger/CompositeKey.java b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ledger/CompositeKey.java new file mode 100644 index 00000000000..c5fbf12b91d --- /dev/null +++ b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ledger/CompositeKey.java @@ -0,0 +1,87 @@ +/* +Copyright IBM 2017 All Rights Reserved. + +Licensed 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.hyperledger.fabric.shim.ledger; + +import static java.util.stream.Collectors.joining; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CompositeKey { + + private static final String DELIMITER = new String(Character.toChars(Character.MIN_CODE_POINT)); + private static final String INVALID_SEGMENT_CHAR = new String(Character.toChars(Character.MAX_CODE_POINT)); + private static final String INVALID_SEGMENT_PATTERN = String.format("(?:%s|%s)", INVALID_SEGMENT_CHAR, DELIMITER); + + final String objectType; + final List attributes; + final String compositeKey; + + public CompositeKey(String objectType, String... attributes) { + this(objectType, attributes == null ? Collections.emptyList() : Arrays.asList(attributes)); + } + + public CompositeKey(String objectType, List attributes) { + if (objectType == null) + throw new NullPointerException("objectType cannot be null"); + this.objectType = objectType; + this.attributes = attributes; + this.compositeKey = generateCompositeKeyString(objectType, attributes); + } + + public String getObjectType() { + return objectType; + } + + public List getAttributes() { + return attributes; + } + + @Override + public String toString() { + return compositeKey; + } + + public static CompositeKey parseCompositeKey(String compositeKey) { + if(compositeKey == null) return null; + final String[] segments = compositeKey.split(DELIMITER, 0); + return new CompositeKey(segments[0], Arrays.stream(segments).skip(1).toArray(String[]::new)); + } + + private String generateCompositeKeyString(String objectType, List attributes) { + + // object type must be a valid composite key segment + validateCompositeKeySegment(objectType); + + // the attributes must be valid composite key segments + attributes.stream().forEach(x -> validateCompositeKeySegment(x)); + + // return objectType + DELIMITER + (attribute + DELIMITER)* + return attributes.stream().collect(joining(DELIMITER, objectType + DELIMITER, DELIMITER)); + + } + + private void validateCompositeKeySegment(String segment) { + final Matcher matcher = Pattern.compile(INVALID_SEGMENT_PATTERN).matcher(segment); + if (matcher.find()) { + throw CompositeKeyFormatException.forInputString(segment, matcher.group(), matcher.start()); + } + } + +} diff --git a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ledger/CompositeKeyFormatException.java b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ledger/CompositeKeyFormatException.java new file mode 100644 index 00000000000..1f0861d4d25 --- /dev/null +++ b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/ledger/CompositeKeyFormatException.java @@ -0,0 +1,40 @@ +/* +Copyright IBM 2017 All Rights Reserved. + +Licensed 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.hyperledger.fabric.shim.ledger; + +class CompositeKeyFormatException extends IllegalArgumentException { + private static final long serialVersionUID = 1L; + + private CompositeKeyFormatException() { + super(); + } + + private CompositeKeyFormatException(String message, Throwable cause) { + super(message, cause); + } + + private CompositeKeyFormatException(String s) { + super(s); + } + + private CompositeKeyFormatException(Throwable cause) { + super(cause); + } + + static CompositeKeyFormatException forInputString(String s, String group, int index) { + return new CompositeKeyFormatException(String.format("For input string '%s', found 'U+%06X' at index %d.", s, group.codePointAt(0), index)); + } +} diff --git a/core/chaincode/shim/java/src/test/java/org/hyperledger/fabric/shim/ledger/CompositeKeyTest.java b/core/chaincode/shim/java/src/test/java/org/hyperledger/fabric/shim/ledger/CompositeKeyTest.java new file mode 100644 index 00000000000..7a076a7c095 --- /dev/null +++ b/core/chaincode/shim/java/src/test/java/org/hyperledger/fabric/shim/ledger/CompositeKeyTest.java @@ -0,0 +1,104 @@ +/* +Copyright IBM 2017 All Rights Reserved. + +Licensed 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.hyperledger.fabric.shim.ledger; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; + +import org.junit.Test; + +public class CompositeKeyTest { + + @Test + public void testCompositeKeyStringStringArray() { + final CompositeKey key = new CompositeKey("abc", new String[] {"def", "ghi", "jkl", "mno"}); + assertThat(key.getObjectType(), is(equalTo("abc"))); + assertThat(key.getAttributes(), hasSize(4)); + assertThat(key.toString(), is(equalTo("abc\u0000def\u0000ghi\u0000jkl\u0000mno\u0000"))); + } + + @Test + public void testCompositeKeyStringListOfString() { + final CompositeKey key = new CompositeKey("abc", Arrays.asList(new String[] {"def", "ghi", "jkl", "mno"})); + assertThat(key.getObjectType(), is(equalTo("abc"))); + assertThat(key.getAttributes(), hasSize(4)); + assertThat(key.toString(), is(equalTo("abc\u0000def\u0000ghi\u0000jkl\u0000mno\u0000"))); + } + + @Test(expected=CompositeKeyFormatException.class) + public void testCompositeKeyWithInvalidObjectTypeDelimiter() { + new CompositeKey("ab\u0000c", Arrays.asList(new String[] {"def", "ghi", "jkl", "mno"})); + } + + @Test(expected=CompositeKeyFormatException.class) + public void testCompositeKeyWithInvalidAttributeDelimiter() { + new CompositeKey("abc", Arrays.asList(new String[] {"def", "ghi", "j\u0000kl", "mno"})); + } + + @Test(expected=CompositeKeyFormatException.class) + public void testCompositeKeyWithInvalidObjectTypeMaxCodePoint() { + new CompositeKey("ab\udbff\udfffc", Arrays.asList(new String[] {"def", "ghi", "jkl", "mno"})); + } + @Test(expected=CompositeKeyFormatException.class) + public void testCompositeKeyWithInvalidAttributeMaxCodePoint() { + new CompositeKey("abc", Arrays.asList(new String[] {"def", "ghi", "jk\udbff\udfffl", "mno"})); + } + + @Test + public void testGetObjectType() { + final CompositeKey key = new CompositeKey("abc", Arrays.asList(new String[] {"def", "ghi", "jkl", "mno"})); + assertThat(key.getObjectType(), is(equalTo("abc"))); + } + + @Test + public void testGetAttributes() { + final CompositeKey key = new CompositeKey("abc", Arrays.asList(new String[] {"def", "ghi", "jkl", "mno"})); + assertThat(key.getObjectType(), is(equalTo("abc"))); + assertThat(key.getAttributes(), hasSize(4)); + assertThat(key.getAttributes(), contains("def", "ghi", "jkl", "mno")); + } + + @Test + public void testToString() { + final CompositeKey key = new CompositeKey("abc", Arrays.asList(new String[] {"def", "ghi", "jkl", "mno"})); + assertThat(key.toString(), is(equalTo("abc\u0000def\u0000ghi\u0000jkl\u0000mno\u0000"))); + } + + @Test + public void testParseCompositeKey() { + final CompositeKey key = CompositeKey.parseCompositeKey("abc\u0000def\u0000ghi\u0000jkl\u0000mno\u0000"); + assertThat(key.getObjectType(), is(equalTo("abc"))); + assertThat(key.getAttributes(), hasSize(4)); + assertThat(key.getAttributes(), contains("def", "ghi", "jkl", "mno")); + assertThat(key.toString(), is(equalTo("abc\u0000def\u0000ghi\u0000jkl\u0000mno\u0000"))); + } + + @Test(expected=CompositeKeyFormatException.class) + public void testParseCompositeKeyInvalidObjectType() { + CompositeKey.parseCompositeKey("ab\udbff\udfffc\u0000def\u0000ghi\u0000jkl\u0000mno\u0000"); + } + + @Test(expected=CompositeKeyFormatException.class) + public void testParseCompositeKeyInvalidAttribute() { + CompositeKey.parseCompositeKey("abc\u0000def\u0000ghi\u0000jk\udbff\udfffl\u0000mno\u0000"); + } + +}