Skip to content

Commit e1e9a0b

Browse files
committed
Java SDK 1.5.1 release
Java SDK 1.5.1 release
1 parent 95fdb3d commit e1e9a0b

File tree

10 files changed

+151
-104
lines changed

10 files changed

+151
-104
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ To get the binaries of this library as distributed by Microsoft, ready for use w
1414
<dependency>
1515
<groupId>com.microsoft.azure</groupId>
1616
<artifactId>azure-documentdb</artifactId>
17-
<version>1.5.0</version>
17+
<version>1.5.1</version>
1818
</dependency>
1919

2020
###Option 2: Source Via Git

changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Changes in 1.5.1 : ##
2+
3+
- Fixed a bug in HashPartitionResolver to generate hash values in little-endian order to be consistent with other SDKs.
4+
15
## Changes in 1.5.0 : ##
26

37
- Added Client-side sharding framework to the SDK. Implemented HashPartionResolver and RangePartitionResolver classes.

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44
<groupId>com.microsoft.azure</groupId>
55
<artifactId>azure-documentdb</artifactId>
6-
<version>1.5.0</version>
6+
<version>1.5.1</version>
77
<name>${project.groupId}:${project.artifactId}</name>
88
<description>Java SDK for Microsoft Azure DocumentDB</description>
99
<url>http://azure.microsoft.com/en-us/services/documentdb/</url>

src/com/microsoft/azure/documentdb/ConsistentHashRing.java

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,31 @@ of this software and associated documentation files (the "Software"), to deal
2424
package com.microsoft.azure.documentdb;
2525

2626
import java.io.UnsupportedEncodingException;
27+
import java.nio.ByteBuffer;
28+
import java.nio.ByteOrder;
29+
import java.util.AbstractMap;
2730
import java.util.ArrayList;
2831
import java.util.Arrays;
32+
import java.util.List;
33+
import java.util.Map;
2934

3035
/**
31-
* The ConsistentHashRing class internally implements a consistent hash ring using the hash function specified
36+
* The ConsistentHashRing class implements a consistent hash ring using the hash function specified
3237
*/
3338
final class ConsistentHashRing {
3439
private HashGenerator hashGenerator;
3540
private Partition[] partitions;
3641
private ArrayList<String> collectionLinks = new ArrayList<String>();
3742

3843
/**
39-
* ConsistentHashRing constructor taking in the collection links, total number of partitions
44+
* ConsistentHashRing constructor taking in the collection links, number of partitions per node
4045
* and hash generator to initialize the ring.
4146
*
4247
* @param collectionLinks the links of collections participating in partitioning.
43-
* @param totalPartitions the total number of partitions.
48+
* @param partitionsPerNode number of partitions per node.
4449
* @param hashGenerator the hash generator to be used for hashing algorithm.
4550
*/
46-
public ConsistentHashRing(Iterable<String> collectionLinks, int totalPartitions, HashGenerator hashGenerator) {
51+
public ConsistentHashRing(Iterable<String> collectionLinks, int partitionsPerNode, HashGenerator hashGenerator) {
4752
if(collectionLinks == null) {
4853
throw new IllegalArgumentException("collectionLinks");
4954
}
@@ -52,16 +57,16 @@ public ConsistentHashRing(Iterable<String> collectionLinks, int totalPartitions,
5257
this.collectionLinks.add(collectionLink);
5358
}
5459

55-
if(totalPartitions < this.collectionLinks.size()) {
56-
throw new IllegalArgumentException("The total number of partitions must be at least the number of collections.");
60+
if(partitionsPerNode <= 0) {
61+
throw new IllegalArgumentException("The partitions per node must greater than 0.");
5762
}
5863

5964
if(hashGenerator == null) {
6065
throw new IllegalArgumentException("hashGenerator");
6166
}
6267

6368
this.hashGenerator = hashGenerator;
64-
this.partitions = this.constructPartitions(this.collectionLinks, totalPartitions);
69+
this.partitions = this.constructPartitions(this.collectionLinks, partitionsPerNode);
6570
}
6671

6772
/**
@@ -112,23 +117,18 @@ private static byte[] getBytes(Object partitionKey) {
112117
* using the hashing algorithm and then finally sorting the partitions based on the hash value.
113118
*
114119
*/
115-
private Partition[] constructPartitions(ArrayList<String> collectionLinks, int totalPartitions) {
120+
private Partition[] constructPartitions(ArrayList<String> collectionLinks, int partitionsPerNode) {
116121
int collectionsNodeCount = collectionLinks.size();
117-
Partition[] partitions = new Partition[totalPartitions];
118-
119-
int partitionsPerNode = totalPartitions/collectionsNodeCount;
120-
int extraPartitions = totalPartitions - (partitionsPerNode * collectionsNodeCount);
122+
Partition[] partitions = new Partition[partitionsPerNode * collectionsNodeCount];
121123

122124
int index = 0;
123125
for(String collectionNode : collectionLinks) {
124126
byte[] hashValue = this.hashGenerator.computeHash(getBytes(collectionNode));
125127

126-
for(int i=0; i < partitionsPerNode + (extraPartitions > 0 ? 1 : 0); ++i) {
128+
for(int i=0; i < partitionsPerNode; ++i) {
127129
partitions[index++] = new Partition(hashValue, collectionNode);
128130
hashValue = this.hashGenerator.computeHash(hashValue);
129131
}
130-
131-
extraPartitions--;
132132
}
133133

134134
Arrays.sort(partitions);
@@ -157,5 +157,21 @@ private static int lowerBoundSearch(Partition[] partitions, byte[] hashValue) {
157157
}
158158

159159
return partitions.length-1;
160-
}
160+
}
161+
162+
/**
163+
* Gets the serialized version of the consistentRing. Added this helper for the test code.
164+
*
165+
*/
166+
List<Map.Entry<String,Long>> getSerializedPartitionList() {
167+
List<Map.Entry<String,Long>> partitionList= new ArrayList<>();
168+
169+
for(int i=0; i<this.partitions.length; i++) {
170+
ByteBuffer wrapped = ByteBuffer.wrap(partitions[i].getHashValue()).order(ByteOrder.LITTLE_ENDIAN);
171+
int num = wrapped.getInt();
172+
partitionList.add(new AbstractMap.SimpleEntry<>(partitions[i].getNode(), (long)num & 0x0FFFFFFFFL));
173+
}
174+
175+
return partitionList;
176+
}
161177
}

src/com/microsoft/azure/documentdb/HashPartitionResolver.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ of this software and associated documentation files (the "Software"), to deal
2424
package com.microsoft.azure.documentdb;
2525

2626
import java.util.ArrayList;
27+
import java.util.List;
28+
import java.util.Map;
2729

2830
/**
2931
* HashPartitionResolver implements partitioning based on the value of a hash function, allowing you to evenly
@@ -105,7 +107,7 @@ public HashPartitionResolver(PartitionKeyExtractor partitionKeyExtractor, Iterab
105107
}
106108

107109
// Initialize the consistent ring to be used for the hashing algorithm by placing the virtual nodes along the ring
108-
this.consistentHashRing = new ConsistentHashRing(this.collectionLinks, this.collectionLinks.size() * numberOfVirtualNodesPerCollection, hashGenerator);
110+
this.consistentHashRing = new ConsistentHashRing(this.collectionLinks, numberOfVirtualNodesPerCollection, hashGenerator);
109111
}
110112

111113
/**
@@ -121,10 +123,6 @@ public String resolveForCreate(Object document) {
121123
throw new IllegalArgumentException("document");
122124
}
123125

124-
if(this.partitionKeyExtractor == null) {
125-
throw new IllegalStateException("Unable to extract partition key from document. Ensure that you have provided a valid PartitionKeyExtractor function.");
126-
}
127-
128126
Object partitionKey = this.partitionKeyExtractor.getPartitionKey(document);
129127
return this.consistentHashRing.getCollectionNode(partitionKey);
130128
}
@@ -147,4 +145,13 @@ public Iterable<String> resolveForRead(Object partitionKey) {
147145
collectionLinks.add(this.consistentHashRing.getCollectionNode(partitionKey));
148146
return collectionLinks;
149147
}
148+
149+
/**
150+
* Gets the serialized version of the consistentRing. Added this helper for the test code.
151+
*
152+
*/
153+
@SuppressWarnings("unused") // used only by test code
154+
private List<Map.Entry<String,Long>> getSerializedPartitionList() {
155+
return this.consistentHashRing.getSerializedPartitionList();
156+
}
150157
}

src/com/microsoft/azure/documentdb/HttpConstants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public static class HttpHeaders {
161161

162162
public static class Versions {
163163
public static String CURRENT_VERSION = "2015-08-06";
164-
public static String USER_AGENT = "documentdb-java-sdk-1.5.0";
164+
public static String USER_AGENT = "documentdb-java-sdk-1.5.1";
165165
}
166166

167167
public static class StatusCodes {

src/com/microsoft/azure/documentdb/MurmurHash.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ of this software and associated documentation files (the "Software"), to deal
2424
package com.microsoft.azure.documentdb;
2525

2626
import java.nio.ByteBuffer;
27+
import java.nio.ByteOrder;
2728

2829
/**
2930
* The MurmurHash3 algorithm was created by Austin Appleby and placed in the public domain.
@@ -46,7 +47,10 @@ public byte[] computeHash(byte[] data) {
4647
}
4748

4849
int hashValue = computeHash(data, data.length, 0);
49-
return ByteBuffer.allocate(4).putInt(hashValue).array();
50+
// Java's default "Endianess" is BigEndian but for all other SDKs
51+
// the default is LittleEndian, so changing the ByteOrder to be LittleEndian
52+
// here so that we can be consistent across all SDKs.
53+
return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(hashValue).array();
5054
}
5155

5256
/** Returns the MurmurHash3_x86_32 hash. */

src/com/microsoft/azure/documentdb/Partition.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,16 @@ private static int CompareHashValues(byte[] hash1, byte[] hash2) {
6060
if(hash1.length != hash2.length)
6161
throw new IllegalArgumentException("Length of hashes doesn't match.");
6262

63+
// Casting "byte" which is 8-bit signed data type to a "char" which is a 16 bit unsigned data type in Java,
64+
// so that they are compared the same way in all SDKs which have the native 8-bit types as unsigned.
65+
66+
// The hash byte array that is returned from ComputeHash method has the MSB at the end of the array
67+
// so comparing the bytes from the end for compare operations.
6368
for (int i = 0; i < hash1.length; i++) {
64-
if (hash1[i] < hash2[i]) {
69+
if ((char)hash1[hash1.length - i - 1] < (char)hash2[hash1.length - i - 1]) {
6570
return -1;
6671
}
67-
else if (hash1[i] > hash2[i]) {
72+
else if ((char)hash1[hash1.length - i - 1] > (char)hash2[hash1.length - i - 1]) {
6873
return 1;
6974
}
7075
}

src/com/microsoft/azure/documentdb/RangePartitionResolver.java

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ public class RangePartitionResolver<T extends Comparable<T>> implements Partitio
3737
private Map<Range<T>, String> partitionMap;
3838

3939
/**
40-
* RangePartitionResolver constructor taking in the PartitionKeyExtractor, a map of Ranges to collection links
41-
* with the type of class used as the partition key.
40+
* RangePartitionResolver constructor taking in the PartitionKeyExtractor, a map of Ranges to collection links.
4241
*
4342
* @param partitionKeyExtractor an instance of class that implements PartitionKeyExtractor interface.
4443
* @param partitionMap the map of ranges to collection links.
@@ -69,10 +68,6 @@ public String resolveForCreate(Object document) {
6968
throw new IllegalArgumentException("document");
7069
}
7170

72-
if(this.partitionKeyExtractor == null) {
73-
throw new UnsupportedOperationException("Unable to extract partition key from document. Ensure that you have provided a valid PartitionKeyExtractor function.");
74-
}
75-
7671
Object partitionKey = this.partitionKeyExtractor.getPartitionKey(document);
7772
Range<T> containingRange = this.getContainingRange(partitionKey);
7873

@@ -92,14 +87,7 @@ public String resolveForCreate(Object document) {
9287
*/
9388
@Override
9489
public Iterable<String> resolveForRead(Object partitionKey) {
95-
Set<Range<T>> intersectingRanges = null;
96-
97-
if (partitionKey == null) {
98-
intersectingRanges = this.partitionMap.keySet();
99-
}
100-
else {
101-
intersectingRanges = this.getIntersectingRanges(partitionKey);
102-
}
90+
Set<Range<T>> intersectingRanges = this.getIntersectingRanges(partitionKey);
10391

10492
ArrayList<String> collectionsLinks = new ArrayList<String>();
10593
for(Range<T> range : intersectingRanges) {
@@ -137,6 +125,10 @@ private Set<Range<T>> getIntersectingRanges(Object partitionKey) {
137125
Set<Range<T>> intersectingRanges = new HashSet<Range<T>>();
138126
Set<Range<T>> partitionKeyRanges = new HashSet<Range<T>>();
139127

128+
if (partitionKey == null) {
129+
return this.partitionMap.keySet();
130+
}
131+
140132
try {
141133
// Check the type of partitionKey to be Range<T>. In Java the type information is erased at runtime due to type erasure.
142134
// We can only check for Range<?> in this case

0 commit comments

Comments
 (0)