/*
* Copyright 2013 NGDATA nv
*
* 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.lilyproject.indexer.hbase.mapper;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.ngdata.hbaseindexer.indexer.Sharder;
import com.ngdata.hbaseindexer.indexer.SharderException;
/**
* Shards ids using the default sharding Lily sharding logic. (i.e. return value is a function of the master id
* contained in the incoming id value)
*/
public class LilySharder implements Sharder {
private int numShards;
private MessageDigest mdAlgorithm;
public LilySharder(int numShards) throws SharderException {
Preconditions.checkArgument(numShards > 0, "There should be at least one shard");
this.numShards = numShards;
try {
this.mdAlgorithm = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new SharderException("failed to initialize MD5 digest", e);
}
}
private long hash(String key) throws SharderException {
try {
// Cloning message digest rather than looking it up each time
MessageDigest md = (MessageDigest) mdAlgorithm.clone();
byte[] digest = md.digest(key.getBytes("UTF-8"));
return ((digest[0] & 0xFF) << 8) + ((digest[1] & 0xFF));
} catch (UnsupportedEncodingException e) {
throw new SharderException("Error calculating hash.", e);
} catch (CloneNotSupportedException e) {
// Sun's MD5 supports cloning, so we don't expect this to happen
throw new RuntimeException(e);
}
}
public int getShard(String id) throws SharderException {
String[] parts = LilySharder.escapedSplit(id, '.');
if (parts.length < 2) {
throw new RuntimeException("Does not look like a Lily id: " + id);
}
// only keep the master record id (part 0 (the type) and part 1)
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(escapeReservedCharacters(parts[0]));
stringBuilder.append(".");
stringBuilder.append(escapeReservedCharacters(parts[1]));
long h = hash(stringBuilder.toString());
return (int) ((h % numShards) + numShards) % numShards;
}
/**
* Borrowed from Lily's IdGenerator class
*/
private static String[] escapedSplit(String s, char delimiter) {
List<String> split = Lists.newArrayList();
StringBuffer sb = new StringBuffer();
boolean escaped = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (escaped) {
escaped = false;
sb.append(c);
} else if (delimiter == c) {
split.add(sb.toString());
sb = new StringBuffer();
} else if ('\\' == c) {
escaped = true;
sb.append(c);
} else {
sb.append(c);
}
}
split.add(sb.toString());
return split.toArray(new String[0]);
}
/**
* Borrowed from Lily's IdGenerator class
*/
protected static String escapeReservedCharacters(String text) {
return text.replaceAll("([.,=\\\\])", "\\\\$1");
}
}