/* * 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.ByteArrayInputStream; import java.io.IOException; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.node.ArrayNode; import org.codehaus.jackson.node.ObjectNode; import org.lilyproject.util.json.JsonFormat; import org.lilyproject.util.json.JsonUtil; /** * Creates a {@link ShardSelector} from a json config. * * <pre> * { * shardingKey: { * value: { * source: "recordId|masterRecordId|variantProperty", * property: "prop name" [only if source = variantProperty] * } * type: "long|string", * hash: "md5", [optional, only if you want the value to be hashed] * modulus: 3, [optional, only possible if type is long] * }, * * mapping: { * type: "list|range", * * in case of list: * * entries: [ * { shard: "shard1", values: [0, 1, 2] }, values in array should be long or string according to type * { shard: "shard2", values: [3, 4, 5] } * ] * * in case of range: * entries: [ * { shard: "shard1", upTo: 1000 }, * { shard: "shard2" } * ] * } * } * </pre> */ public class JsonShardSelectorBuilder { private JsonShardSelectorBuilder() { } public static ShardSelector build(byte[] configData) throws ShardingConfigException { JsonNode node; try { node = JsonFormat.deserializeNonStd(new ByteArrayInputStream(configData)); } catch (IOException e) { throw new ShardingConfigException("Error reading the sharding configuration.", e); } if (node.isObject()) { return build((ObjectNode)node); } else { throw new ShardingConfigException("The sharding config should be a JSON object."); } } public static ShardSelector build(ObjectNode configNode) throws ShardingConfigException { // // Build the sharding key // ObjectNode shardingKeyNode = JsonUtil.getObject(configNode, "shardingKey"); String hash = JsonUtil.getString(shardingKeyNode, "hash", null); boolean enableHashing = false; if (hash != null && hash.equalsIgnoreCase("MD5")) { enableHashing = true; } else if (hash != null) { throw new ShardingConfigException("Unsupported hash algorithm: " + hash); } String shardingKeyTypeName = JsonUtil.getString(shardingKeyNode, "type"); ShardingKey.KeyType keyType; if (shardingKeyTypeName.equalsIgnoreCase("string")) { keyType = ShardingKey.KeyType.STRING; } else if (shardingKeyTypeName.equalsIgnoreCase("long")) { keyType = ShardingKey.KeyType.LONG; } else { throw new ShardingConfigException("Invalid sharding key type: " + shardingKeyTypeName); } int modulus = JsonUtil.getInt(shardingKeyNode, "modulus", 0); if (modulus > 0 && keyType != ShardingKey.KeyType.LONG) { throw new ShardingConfigException("modulus is only allowed if sharding key type is long"); } ObjectNode shardingValue = JsonUtil.getObject(shardingKeyNode, "value"); String shardingValueSource = JsonUtil.getString(shardingValue, "source"); ShardingKey shardingKey; if (shardingValueSource.equals("masterRecordId")) { shardingKey = ShardingKey.masterRecordIdShardingKey(enableHashing, modulus, keyType); } else if (shardingValueSource.equals("recordId")) { shardingKey = ShardingKey.recordIdShardingKey(enableHashing, modulus, keyType); } else if (shardingValueSource.equals("variantProperty")) { String property = JsonUtil.getString(shardingValue, "property"); shardingKey = ShardingKey.variantProperyShardingKey(property, enableHashing, modulus, keyType); } else { throw new ShardingConfigException("Invalid sharding key value source: " + shardingValueSource); } // // Build the mapping // ObjectNode mappingNode = JsonUtil.getObject(configNode, "mapping"); String mappingTypeName = JsonUtil.getString(mappingNode, "type"); ShardSelector selector; if (mappingTypeName.equals("range")) { selector = new RangeShardSelector(shardingKey); } else if (mappingTypeName.equals("list")) { selector = new ListShardSelector(shardingKey); } else { throw new ShardingConfigException("Unsupported mappingType: " + mappingTypeName); } ArrayNode mappingEntriesNode = JsonUtil.getArray(mappingNode, "entries"); if (mappingEntriesNode.size() == 0) { throw new ShardingConfigException("Mapping does not contain any entries."); } for (int i = 0; i < mappingEntriesNode.size(); i++) { JsonNode node = mappingEntriesNode.get(i); if (!node.isObject()) { throw new ShardingConfigException("The entries within the mapping array should be json objects."); } String shardName = JsonUtil.getString(node, "shard"); if (mappingTypeName.equals("range")) { Comparable upToValue = null; JsonNode upTo = node.get("upTo"); if (upTo == null && i != mappingEntriesNode.size() - 1) { throw new ShardingConfigException("upTo value is not specified in mapping for the non-last" + " mapping, shard name: " + shardName); } else if (upTo == null) { // ok, can be null for last mapping to indicate unbounded range } else { upToValue = getValue(keyType, upTo); } ((RangeShardSelector)selector).addMapping(shardName, upToValue); } else { ArrayNode valuesNode = JsonUtil.getArray(node, "values"); for (int j = 0; j < valuesNode.size(); j++) { Comparable value = getValue(keyType, valuesNode.get(j)); ((ListShardSelector)selector).addMapping(value, shardName); } } } return selector; } private static Comparable getValue(ShardingKey.KeyType keyType, JsonNode value) throws ShardingConfigException { Comparable result; switch (keyType) { case STRING: if (value.isTextual()) { result = value.getTextValue(); } else { throw new ShardingConfigException("Values specified in the mapping should correspond to the" + " sharding key type, in this case string."); } break; case LONG: if (value.isLong() || value.isInt()) { result = value.getLongValue(); } else { throw new ShardingConfigException("Values specified in the mapping should correspond to the" + " sharding key type, in this case long."); } break; default: throw new RuntimeException("Unexpected sharding key type: " + keyType); } return result; } }