| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.brooklyn.entity.nosql.cassandra; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| |
| import java.io.Serializable; |
| import java.math.BigInteger; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.brooklyn.util.collections.MutableList; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| |
| public class TokenGenerators { |
| |
| /** |
| * Sub-classes are recommended to call {@link #checkRangeValid()} at construction time. |
| */ |
| public static abstract class AbstractTokenGenerator implements TokenGenerator, Serializable { |
| |
| private static final long serialVersionUID = -1884526356161711176L; |
| |
| public static final BigInteger TWO = BigInteger.valueOf(2); |
| |
| @Override |
| public abstract BigInteger max(); |
| @Override |
| public abstract BigInteger min(); |
| @Override |
| public abstract BigInteger range(); |
| |
| private final Set<BigInteger> currentTokens = Sets.newTreeSet(); |
| private final List<BigInteger> nextTokens = Lists.newArrayList(); |
| private BigInteger origin = BigInteger.ZERO; |
| |
| protected void checkRangeValid() { |
| Preconditions.checkState(range().equals(max().subtract(min()).add(BigInteger.ONE)), |
| "min=%s; max=%s; range=%s", min(), max(), range()); |
| } |
| |
| @Override |
| public void setOrigin(BigInteger shift) { |
| this.origin = Preconditions.checkNotNull(shift, "shift"); |
| } |
| |
| /** |
| * Unless we're explicitly starting a new cluster or resizing by a pre-defined number of nodes, then |
| * let Cassandra decide (i.e. return null). |
| */ |
| @Override |
| public synchronized BigInteger newToken() { |
| BigInteger result = (nextTokens.isEmpty()) ? null : nextTokens.remove(0); |
| if (result != null) currentTokens.add(result); |
| return result; |
| } |
| |
| @Override |
| public synchronized BigInteger getTokenForReplacementNode(BigInteger oldToken) { |
| checkNotNull(oldToken, "oldToken"); |
| return normalize(oldToken.subtract(BigInteger.ONE)); |
| } |
| |
| @Override |
| public synchronized Set<BigInteger> getTokensForReplacementNode(Set<BigInteger> oldTokens) { |
| checkNotNull(oldTokens, "oldToken"); |
| Set<BigInteger> result = Sets.newLinkedHashSet(); |
| for (BigInteger oldToken : oldTokens) { |
| result.add(getTokenForReplacementNode(oldToken)); |
| } |
| return result; |
| } |
| |
| @Override |
| public synchronized void growingCluster(int numNewNodes) { |
| if (currentTokens.isEmpty() && nextTokens.isEmpty()) { |
| nextTokens.addAll(generateEquidistantTokens(numNewNodes)); |
| } else { |
| // simple strategy which iteratively finds best midpoint |
| for (int i=0; i<numNewNodes; i++) { |
| nextTokens.add(generateBestNextToken()); |
| } |
| } |
| } |
| |
| @Override |
| public synchronized void shrinkingCluster(Set<BigInteger> nodesToRemove) { |
| currentTokens.remove(nodesToRemove); |
| } |
| |
| @Override |
| public synchronized void refresh(Set<BigInteger> currentNodes) { |
| currentTokens.clear(); |
| currentTokens.addAll(currentNodes); |
| } |
| |
| private List<BigInteger> generateEquidistantTokens(int numTokens) { |
| List<BigInteger> result = Lists.newArrayList(); |
| for (int i = 0; i < numTokens; i++) { |
| BigInteger token = range().multiply(BigInteger.valueOf(i)).divide(BigInteger.valueOf(numTokens)).add(min()); |
| token = normalize(token.add(origin)); |
| result.add(token); |
| } |
| return result; |
| } |
| |
| private BigInteger normalize(BigInteger input) { |
| while (input.compareTo(min()) < 0) |
| input = input.add(range()); |
| while (input.compareTo(max()) > 0) |
| input = input.subtract(range()); |
| return input; |
| } |
| |
| private BigInteger generateBestNextToken() { |
| List<BigInteger> allTokens = MutableList.<BigInteger>of().appendAll(currentTokens).appendAll(nextTokens); |
| Collections.sort(allTokens); |
| Iterator<BigInteger> ti = allTokens.iterator(); |
| |
| BigInteger thisValue = ti.next(); |
| BigInteger prevValue = allTokens.get(allTokens.size()-1).subtract(range()); |
| |
| BigInteger bestNewTokenSoFar = normalize(prevValue.add(thisValue).divide(TWO)); |
| BigInteger biggestRangeSizeSoFar = thisValue.subtract(prevValue); |
| |
| while (ti.hasNext()) { |
| prevValue = thisValue; |
| thisValue = ti.next(); |
| |
| BigInteger rangeHere = thisValue.subtract(prevValue); |
| if (rangeHere.compareTo(biggestRangeSizeSoFar) > 0) { |
| bestNewTokenSoFar = prevValue.add(thisValue).divide(TWO); |
| biggestRangeSizeSoFar = rangeHere; |
| } |
| } |
| return bestNewTokenSoFar; |
| } |
| |
| } |
| |
| public static class PosNeg63TokenGenerator extends AbstractTokenGenerator { |
| private static final long serialVersionUID = 7327403957176106754L; |
| |
| public static final BigInteger MIN_TOKEN = TWO.pow(63).negate(); |
| public static final BigInteger MAX_TOKEN = TWO.pow(63).subtract(BigInteger.ONE); |
| public static final BigInteger RANGE = TWO.pow(64); |
| |
| public PosNeg63TokenGenerator() { |
| checkRangeValid(); |
| } |
| |
| @Override public BigInteger max() { return MAX_TOKEN; } |
| @Override public BigInteger min() { return MIN_TOKEN; } |
| @Override public BigInteger range() { return RANGE; } |
| } |
| |
| /** token generator used by cassandra pre v1.2 */ |
| public static class NonNeg127TokenGenerator extends AbstractTokenGenerator { |
| private static final long serialVersionUID = 1357426905711548198L; |
| |
| public static final BigInteger MIN_TOKEN = BigInteger.ZERO; |
| public static final BigInteger MAX_TOKEN = TWO.pow(127).subtract(BigInteger.ONE); |
| public static final BigInteger RANGE = TWO.pow(127); |
| |
| public NonNeg127TokenGenerator() { |
| checkRangeValid(); |
| } |
| |
| @Override public BigInteger max() { return MAX_TOKEN; } |
| @Override public BigInteger min() { return MIN_TOKEN; } |
| @Override public BigInteger range() { return RANGE; } |
| } |
| |
| } |