/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.wso2.siddhi.core.util.transport;
import org.wso2.siddhi.core.config.ExecutionPlanContext;
import org.wso2.siddhi.core.exception.ConnectionUnavailableException;
import org.wso2.siddhi.core.stream.output.sink.Sink;
import org.wso2.siddhi.core.stream.output.sink.distributed.DistributedTransport;
import org.wso2.siddhi.core.util.SiddhiClassLoader;
import org.wso2.siddhi.core.util.SiddhiConstants;
import org.wso2.siddhi.core.util.config.ConfigReader;
import org.wso2.siddhi.core.util.extension.holder.SinkExecutorExtensionHolder;
import org.wso2.siddhi.core.util.parser.helper.DefinitionParserHelper;
import org.wso2.siddhi.query.api.annotation.Annotation;
import org.wso2.siddhi.query.api.exception.ExecutionPlanValidationException;
import org.wso2.siddhi.query.api.extension.Extension;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class implements a the distributed sink that could publish to multiple destination using a single
* client/publisher. Following are some examples,
* - In a case where there are multiple partitions in a single topic in Kafka, the same kafka client can be used
* to send events to all the partitions within the topic.
* - The same email client can send email to different addresses.
*/
public class SingleClientDistributedSink extends DistributedTransport {
private Sink sink;
private int destinationCount = 0;
@Override
public void publish(Object payload, DynamicOptions transportOptions, int destinationId)
throws ConnectionUnavailableException {
try {
transportOptions.setVariableOptionIndex(destinationId);
sink.publish(payload, transportOptions);
} catch (ConnectionUnavailableException e) {
strategy.destinationFailed(destinationId);
throw e;
}
}
@Override
public void initTransport(OptionHolder sinkOptionHolder, List<OptionHolder> destinationOptionHolders,
Annotation sinkAnnotation, ConfigReader sinkConfigReader,
ExecutionPlanContext executionPlanContext) {
final String transportType = sinkOptionHolder.validateAndGetStaticValue(SiddhiConstants
.ANNOTATION_ELEMENT_TYPE);
Extension sinkExtension = DefinitionParserHelper.constructExtension
(streamDefinition, SiddhiConstants.ANNOTATION_SINK, transportType, sinkAnnotation, SiddhiConstants
.NAMESPACE_SINK);
Set<String> allDynamicOptionKeys = findAllDynamicOptions(destinationOptionHolders);
destinationOptionHolders.forEach(optionHolder -> {
optionHolder.merge(sinkOptionHolder);
allDynamicOptionKeys.forEach(optionKey -> {
String optionValue = optionHolder.getOrCreateOption(optionKey, null).getValue();
if (optionValue == null || optionValue.isEmpty()) {
throw new ExecutionPlanValidationException("Destination properties can only contain " +
"non-empty static values.");
}
Option sinkOption = sinkOptionHolder.getOrAddStaticOption(optionKey, optionValue);
sinkOption.addVariableValue(optionValue);
destinationCount++;
});
});
this.sink = (Sink) SiddhiClassLoader.loadExtensionImplementation(
sinkExtension, SinkExecutorExtensionHolder.getInstance(executionPlanContext));
this.sink.initOnlyTransport(streamDefinition, sinkOptionHolder, sinkConfigReader, executionPlanContext);
}
/**
* Will be called to connect to the backend before events are published
*
* @throws ConnectionUnavailableException if it cannot connect to the backend
*/
@Override
public void connect() throws ConnectionUnavailableException {
sink.connect();
for (int i = 0; i < destinationCount; i++) {
strategy.destinationAvailable(i);
}
}
/**
* Will be called after all publishing is done, or when ConnectionUnavailableException is thrown
*/
@Override
public void disconnect() {
sink.disconnect();
}
/**
* Will be called at the end to clean all the resources consumed
*/
@Override
public void destroy() {
sink.destroy();
}
/**
* Used to collect the serializable state of the processing element, that need to be
* persisted for the reconstructing the element to the same state on a different point of time
*
* @return stateful objects of the processing element as an array
*/
@Override
public Map<String, Object> currentState() {
return sink.currentState();
}
/**
* Used to restore serialized state of the processing element, for reconstructing
* the element to the same state as if was on a previous point of time.
*
* @param state the stateful objects of the element as an array on
* the same order provided by currentState().
*/
@Override
public void restoreState(Map<String, Object> state) {
sink.restoreState(state);
}
private Set<String> findAllDynamicOptions(List<OptionHolder> destinationOptionHolders) {
Set<String> dynamicOptions = new HashSet<>();
destinationOptionHolders.forEach(destinationOptionHolder -> {
destinationOptionHolder.getDynamicOptionsKeys().forEach(dynamicOptions::add);
destinationOptionHolder.getStaticOptionsKeys().forEach(dynamicOptions::add);
});
return dynamicOptions;
}
}