/* * Copyright 2016-2017 the original author or authors. * * 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.springframework.cloud.stream.binder; import org.springframework.expression.EvaluationContext; import org.springframework.messaging.Message; import org.springframework.util.Assert; /** * Utility class to determine if a binding is configured for partitioning * (based on the binder properties provided in the constructor) and * what partition a message should be delivered to. * * @author Patrick Peralta * @author David Turanski * @author Gary Russell * @author Ilayaperumal Gopinathan * @author Mark Fisher * @author Marius Bogoevici */ public class PartitionHandler { private final EvaluationContext evaluationContext; private final ProducerProperties producerProperties; private final PartitionKeyExtractorStrategy partitionKeyExtractorStrategy; private final PartitionSelectorStrategy partitionSelectorStrategy; /** * Construct a {@code PartitionHandler}. * * @param evaluationContext evaluation context for binder * @param properties binder properties * @param partitionKeyExtractorStrategy PartitionKeyExtractor strategy * @param partitionSelectorStrategy PartitionSelector strategy */ public PartitionHandler(EvaluationContext evaluationContext, ProducerProperties properties, PartitionKeyExtractorStrategy partitionKeyExtractorStrategy, PartitionSelectorStrategy partitionSelectorStrategy) { this.evaluationContext = evaluationContext; this.producerProperties = properties; this.partitionKeyExtractorStrategy = partitionKeyExtractorStrategy; this.partitionSelectorStrategy = partitionSelectorStrategy; } /** * Determine the partition to which to send this message. * <p> * If a partition key extractor class is provided, it is invoked to determine * the key. Otherwise, the partition key expression is evaluated to obtain the * key value. * <p> * If a partition selector class is provided, it will be invoked to determine the * partition. Otherwise, if the partition expression is not null, it is evaluated * against the key and is expected to return an integer to which the modulo * function will be applied, using the {@code partitionCount} as the divisor. If no * partition expression is provided, the key will be passed to the binder * partition strategy along with the {@code partitionCount}. The default partition * strategy uses {@code key.hashCode()}, and the result will be the mod of that value. * * @param message the message. * @return the partition */ public int determinePartition(Message<?> message) { Object key = extractKey(message); int partition; if (this.producerProperties.getPartitionSelectorExpression() != null) { partition = this.producerProperties.getPartitionSelectorExpression().getValue( this.evaluationContext, key, Integer.class); } else { partition = this.partitionSelectorStrategy.selectPartition(key, producerProperties.getPartitionCount()); } // protection in case a user selector returns a negative. return Math.abs(partition % producerProperties.getPartitionCount()); } private Object extractKey(Message<?> message) { Object key = null; if (this.producerProperties.getPartitionKeyExtractorClass() != null) { key = invokeKeyExtractor(message); } else if (this.producerProperties.getPartitionKeyExpression() != null) { key = this.producerProperties.getPartitionKeyExpression().getValue(this.evaluationContext, message); } Assert.notNull(key, "Partition key cannot be null"); return key; } private Object invokeKeyExtractor(Message<?> message) { return this.partitionKeyExtractorStrategy.extractKey(message); } private int invokePartitionSelector(Object key) { return this.partitionSelectorStrategy.selectPartition(key, producerProperties.getPartitionCount()); } }