/* * Copyright 2010 Outerthought bvba * * 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.model.sharding; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.lilyproject.repository.api.RecordId; public class ShardingKey { private ShardingKeyValue value; /** True if hash should be calculated. */ private boolean hash; /** If > 0, calculate modulus after hash. */ private int modulus; private KeyType keyType; private final MessageDigest mdAlgorithm; enum KeyType { STRING, LONG } private ShardingKey(ShardingKeyValue value, boolean hash, int modulus, KeyType keyType) { this.value = value; this.hash = hash; this.modulus = modulus; this.keyType = keyType; try { mdAlgorithm = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } public static ShardingKey recordIdShardingKey(boolean hash, int modulus, KeyType keyType) { return new ShardingKey(new RecordIdShardingKeyValue(), hash, modulus, keyType); } public static ShardingKey masterRecordIdShardingKey(boolean hash, int modulus, KeyType keyType) { return new ShardingKey(new MasterRecordIdShardingKeyValue(), hash, modulus, keyType); } public static ShardingKey variantProperyShardingKey(String propertyName, boolean hash, int modulus, KeyType keyType) { return new ShardingKey(new VariantPropertyShardingKeyValue(propertyName), hash, modulus, keyType); } public Comparable getShardingKey(RecordId recordId) throws ShardSelectorException { Object key = value.getValue(recordId); if (hash) { key = hash(key.toString()); } switch (keyType) { case STRING: key = key.toString(); break; case LONG: if (key instanceof Number) { key = ((Number)key).longValue(); } else { try { key = Long.parseLong(key.toString()); } catch (NumberFormatException e) { throw new ShardSelectorException("Error parsing sharding key as long. Value: " + key, e); } } break; } if (modulus > 0) { key = ((Long)key) % modulus; } return (Comparable)key; } private long hash(String key) throws ShardSelectorException { 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 ShardSelectorException("Error calculating hash.", e); } catch (CloneNotSupportedException e) { // Sun's MD5 supports cloning, so we don't expect this to happen throw new RuntimeException(e); } } private interface ShardingKeyValue { String getValue(RecordId recordId) throws ShardSelectorException; } private static class RecordIdShardingKeyValue implements ShardingKeyValue { @Override public String getValue(RecordId recordId) { return recordId.toString(); } } private static class MasterRecordIdShardingKeyValue implements ShardingKeyValue { @Override public String getValue(RecordId recordId) { return recordId.getMaster().toString(); } } private static class VariantPropertyShardingKeyValue implements ShardingKeyValue { private String propertyName; VariantPropertyShardingKeyValue(String propertyName) { this.propertyName = propertyName; } @Override public String getValue(RecordId recordId) throws ShardSelectorException { String propertyValue = recordId.getVariantProperties().get(propertyName); if (propertyValue == null) { throw new ShardSelectorException("Variant property used for sharding has no value. Property " + propertyName + " in record id: " + recordId); } return propertyValue; } } }