/*
* Copyright 2014 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.xd.dirt.server.admin.deployment;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.xd.dirt.core.Stream;
import org.springframework.xd.dirt.integration.bus.BusProperties;
import org.springframework.xd.module.ModuleDeploymentProperties;
import org.springframework.xd.module.ModuleDescriptor;
import org.springframework.xd.module.RuntimeModuleDeploymentProperties;
/**
* {@link RuntimeModuleDeploymentPropertiesProvider} that provides the stream partition/short circuit
* properties for the given {@link ModuleDescriptor}.
*
* @author Patrick Peralta
* @author Mark Fisher
* @author Ilayaperumal Gopinathan
* @author Marius Bogoevici
*/
public class StreamRuntimePropertiesProvider extends RuntimeModuleDeploymentPropertiesProvider {
/**
* Logger.
*/
private static final Logger logger = LoggerFactory.getLogger(StreamRuntimePropertiesProvider.class);
/**
* The stream to create properties for.
*/
private final Stream stream;
/**
* Construct a {@code StreamPartitionPropertiesProvider} for
* a {@link org.springframework.xd.dirt.core.Stream}.
*
* @param stream stream to create partition properties for
*/
public StreamRuntimePropertiesProvider(Stream stream,
ModuleDeploymentPropertiesProvider<ModuleDeploymentProperties> propertiesProvider) {
super(propertiesProvider);
this.stream = stream;
}
/**
* {@inheritDoc}
*/
@Override
public RuntimeModuleDeploymentProperties propertiesForDescriptor(ModuleDescriptor moduleDescriptor) {
List<ModuleDescriptor> streamModules = stream.getModuleDescriptors();
RuntimeModuleDeploymentProperties properties = super.propertiesForDescriptor(moduleDescriptor);
int moduleSequence = properties.getSequence();
int moduleIndex = moduleDescriptor.getIndex();
// not first or input channel is named
if (moduleIndex > 0 || isNamedChannelInput(moduleDescriptor)) {
properties.put("consumer." + BusProperties.SEQUENCE, String.valueOf(moduleSequence));
properties.put("consumer." + BusProperties.COUNT, String.valueOf(properties.getCount()));
}
if (moduleIndex > 0) {
ModuleDescriptor previous = streamModules.get(moduleIndex - 1);
ModuleDeploymentProperties previousProperties = deploymentPropertiesProvider.propertiesForDescriptor(previous);
if (hasPartitionKeyProperty(previousProperties)) {
properties.put("consumer." + BusProperties.PARTITION_INDEX, String.valueOf(moduleSequence - 1));
}
String minPartitionCount = previousProperties.get("producer." + BusProperties.MIN_PARTITION_COUNT);
if (minPartitionCount != null) {
properties.put("consumer." + BusProperties.MIN_PARTITION_COUNT, minPartitionCount);
}
}
// Not last
if (moduleIndex + 1 < streamModules.size()) {
ModuleDeploymentProperties nextProperties = deploymentPropertiesProvider.propertiesForDescriptor(streamModules.get(moduleIndex + 1));
String count = nextProperties.get(ModuleDeploymentProperties.COUNT_KEY);
if (count != null) {
properties.put("producer." + BusProperties.NEXT_MODULE_COUNT, count);
}
String concurrency = nextProperties.get("consumer."+BusProperties.CONCURRENCY);
if (concurrency != null) {
properties.put("producer." + BusProperties.NEXT_MODULE_CONCURRENCY, concurrency);
}
}
// Partitioning
if (hasPartitionKeyProperty(properties)) {
if (moduleIndex == streamModules.size() && !isNamedChannelOutput(moduleDescriptor)) {
logger.warn("Module '{}' is a sink module which contains a property " +
"of '{}' used for data partitioning; this feature is only " +
"supported for modules that produce data", moduleDescriptor,
"producer.partitionKeyExpression");
}
}
else if (moduleIndex + 1 < streamModules.size()) {
// check for direct binding if the module is neither last nor partitioned
ModuleDeploymentProperties nextProperties =
deploymentPropertiesProvider.propertiesForDescriptor(streamModules.get(moduleIndex + 1));
/*
* A direct binding is allowed if all of the following are true:
* 1. the user did not explicitly disallow direct binding
* 2. this module is not a partitioning producer
* 3. this module is not the last one in a stream
* 4. both this module and the next module have a count of 0
* 5. both this module and the next module have the same criteria (both can be null)
*/
String directBindingKey = "producer." + BusProperties.DIRECT_BINDING_ALLOWED;
String directBindingValue = properties.get(directBindingKey);
if (directBindingValue != null && !"false".equalsIgnoreCase(directBindingValue)) {
logger.warn(
"Only 'false' is allowed as an explicit value for the {} property, but the value was: '{}'",
directBindingKey, directBindingValue);
}
if (!"false".equalsIgnoreCase(properties.get(directBindingKey))) {
if (properties.getCount() == 0 && nextProperties.getCount() == 0) {
String criteria = properties.getCriteria();
if ((criteria == null && nextProperties.getCriteria() == null)
|| (criteria != null && criteria.equals(nextProperties.getCriteria()))) {
properties.put(directBindingKey, Boolean.toString(true));
}
}
}
}
return properties;
}
private boolean isNamedChannelInput(ModuleDescriptor moduleDescriptor) {
String sourceChannelName = moduleDescriptor.getSourceChannelName();
return sourceChannelName != null
&& (sourceChannelName.startsWith("tap:")
|| sourceChannelName.startsWith("topic:")
|| sourceChannelName.startsWith("queue:"));
}
private boolean isNamedChannelOutput(ModuleDescriptor moduleDescriptor) {
String sinkChannelName = moduleDescriptor.getSinkChannelName();
return sinkChannelName != null
&& (sinkChannelName.startsWith("topic:")
|| sinkChannelName.startsWith("queue:"));
}
/**
* Return {@code true} if the provided properties include a property
* used to extract a partition key.
*
* @param properties properties to examine for a partition key property
* @return true if the properties contain a partition key property
*/
private boolean hasPartitionKeyProperty(ModuleDeploymentProperties properties) {
return (properties.containsKey("producer.partitionKeyExpression")
|| properties.containsKey("producer.partitionKeyExtractorClass"));
}
}