/*
* Copyright 2016 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 io.atomix.group.messaging.internal;
import io.atomix.catalyst.concurrent.BlockingFuture;
import io.atomix.group.internal.GroupCommands;
import io.atomix.group.messaging.MessageFailedException;
import io.atomix.group.messaging.MessageProducer;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
/**
* Abstract message producer.
*
* @author <a href="http://github.com/kuujo>Jordan Halterman</a>
*/
public abstract class AbstractMessageProducer<T> implements MessageProducer<T> {
private final int id;
private final String name;
private final Delivery delivery;
private final Execution execution;
private final AbstractMessageClient client;
private long messageId;
private final Map<Long, CompletableFuture> messageFutures = new ConcurrentHashMap<>();
protected AbstractMessageProducer(String name, Options options, AbstractMessageClient client) {
this.name = name;
this.delivery = options.getDelivery();
this.execution = options.getExecution();
this.client = client;
this.id = client.producerService().registry().register(this);
}
/**
* Returns the producer name.
*
* @return The producer name.
*/
String name() {
return name;
}
/**
* Called when a message acknowledgement is received.
*
* @param ack The message acknowledgement.
*/
@SuppressWarnings("unchecked")
void onAck(GroupCommands.Ack ack) {
CompletableFuture messageFuture = messageFutures.remove(messageId);
if (messageFuture != null) {
if (execution == Execution.SYNC) {
if (ack.succeeded()) {
messageFuture.complete(null);
} else {
messageFuture.completeExceptionally(new MessageFailedException("message failed"));
}
} else if (execution == Execution.REQUEST_REPLY) {
if (ack.succeeded()) {
messageFuture.complete(ack.message());
} else {
messageFuture.completeExceptionally(new MessageFailedException("message failed"));
}
}
}
}
/**
* Submits the message to the given member.
*/
@SuppressWarnings("unchecked")
protected <U> CompletableFuture<U> send(String member, T message) {
if (execution == Execution.ASYNC) {
return sendAsync(member, message);
} else {
return sendSync(member, message);
}
}
/**
* Sends an atomic message.
*/
private CompletableFuture sendSync(String member, T message) {
CompletableFuture future = new BlockingFuture();
final long messageId = ++this.messageId;
messageFutures.put(messageId, future);
client.producerService().send(new GroupCommands.Message(member, id, name, messageId, message, delivery, execution)).whenComplete((result, error) -> {
if (error != null) {
CompletableFuture messageFuture = messageFutures.remove(messageId);
if (messageFuture != null) {
messageFuture.completeExceptionally(error);
}
}
});
return future;
}
/**
* Sends a sequential message.
*/
private CompletableFuture sendAsync(String member, T message) {
return client.producerService().send(new GroupCommands.Message(member, id, name, ++messageId, message, delivery, execution));
}
@Override
public void close() {
client.producerService().registry().close(id);
}
}