/*
* Copyright (C) 2011 Google Inc.
*
* 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.ros.internal.node.topic;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import org.apache.commons.logging.Log;
import org.ros.concurrent.ListenerGroup;
import org.ros.concurrent.SignalRunnable;
import org.ros.internal.node.server.NodeIdentifier;
import org.ros.internal.transport.ProtocolNames;
import org.ros.internal.transport.queue.IncomingMessageQueue;
import org.ros.internal.transport.tcp.TcpRosClientManager;
import org.ros.log.RosLogFactory;
import org.ros.message.MessageDeserializer;
import org.ros.message.MessageListener;
import org.ros.node.topic.DefaultSubscriberListener;
import org.ros.node.topic.Publisher;
import org.ros.node.topic.Subscriber;
import org.ros.node.topic.SubscriberListener;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Default implementation of a {@link Subscriber}.
*
* @author damonkohler@google.com (Damon Kohler)
*/
public class DefaultSubscriber<T> extends DefaultTopicParticipant implements Subscriber<T> {
private static final Log log = RosLogFactory.getLog(DefaultSubscriber.class);
/**
* The maximum delay before shutdown will begin even if all
* {@link SubscriberListener}s have not yet returned from their
* {@link SubscriberListener#onShutdown(Subscriber)} callback.
*/
private static final int DEFAULT_SHUTDOWN_TIMEOUT = 5;
private static final TimeUnit DEFAULT_SHUTDOWN_TIMEOUT_UNITS = TimeUnit.SECONDS;
private final NodeIdentifier nodeIdentifier;
private final ScheduledExecutorService executorService;
private final IncomingMessageQueue<T> incomingMessageQueue;
private final Set<PublisherIdentifier> knownPublishers;
private final TcpRosClientManager tcpClientManager;
private final Object mutex;
/**
* This latch will tell us when there has been proper unregistration from the
* master and all listeners have been signaled of shutdown.
*/
private final CountDownLatch shutdownLatch;
/**
* Manages the {@link SubscriberListener}s for this {@link Subscriber}.
*/
private final ListenerGroup<SubscriberListener<T>> subscriberListeners;
public static <S> DefaultSubscriber<S> newDefault(NodeIdentifier nodeIdentifier,
TopicDeclaration description, ScheduledExecutorService executorService,
MessageDeserializer<S> deserializer) {
return new DefaultSubscriber<S>(nodeIdentifier, description, deserializer, executorService);
}
private DefaultSubscriber(NodeIdentifier nodeIdentifier, TopicDeclaration topicDeclaration,
MessageDeserializer<T> deserializer, ScheduledExecutorService executorService) {
super(topicDeclaration);
this.nodeIdentifier = nodeIdentifier;
this.executorService = executorService;
incomingMessageQueue = new IncomingMessageQueue<T>(deserializer, executorService);
knownPublishers = Sets.newHashSet();
tcpClientManager = new TcpRosClientManager(executorService);
mutex = new Object();
SubscriberHandshakeHandler<T> subscriberHandshakeHandler =
new SubscriberHandshakeHandler<T>(toDeclaration().toConnectionHeader(),
incomingMessageQueue, executorService);
tcpClientManager.addNamedChannelHandler(subscriberHandshakeHandler);
subscriberListeners = new ListenerGroup<SubscriberListener<T>>(executorService);
subscriberListeners.add(new DefaultSubscriberListener<T>() {
@Override
public void onMasterRegistrationSuccess(Subscriber<T> registrant) {
log.info("Subscriber registered: " + DefaultSubscriber.this);
}
@Override
public void onMasterRegistrationFailure(Subscriber<T> registrant) {
log.info("Subscriber registration failed: " + DefaultSubscriber.this);
}
@Override
public void onMasterUnregistrationSuccess(Subscriber<T> registrant) {
log.info("Subscriber unregistered: " + DefaultSubscriber.this);
}
@Override
public void onMasterUnregistrationFailure(Subscriber<T> registrant) {
log.info("Subscriber unregistration failed: " + DefaultSubscriber.this);
}
});
shutdownLatch = new CountDownLatch(2);
}
public SubscriberIdentifier toIdentifier() {
return new SubscriberIdentifier(nodeIdentifier, getTopicDeclaration().getIdentifier());
}
public SubscriberDeclaration toDeclaration() {
return new SubscriberDeclaration(toIdentifier(), getTopicDeclaration());
}
public Collection<String> getSupportedProtocols() {
return ProtocolNames.SUPPORTED;
}
@Override
public boolean getLatchMode() {
return incomingMessageQueue.getLatchMode();
}
@Override
public void addMessageListener(MessageListener<T> messageListener, int limit) {
incomingMessageQueue.addListener(messageListener, limit);
}
@Override
public void addMessageListener(MessageListener<T> messageListener) {
addMessageListener(messageListener, 1);
}
@VisibleForTesting
public void addPublisher(PublisherIdentifier publisherIdentifier, InetSocketAddress address) {
synchronized (mutex) {
// TODO(damonkohler): If the connection is dropped, knownPublishers
// should
// be updated.
if (knownPublishers.contains(publisherIdentifier)) {
return;
}
tcpClientManager.connect(toString(), address);
// TODO(damonkohler): knownPublishers is duplicate information that
// is
// already available to the TopicParticipantManager.
knownPublishers.add(publisherIdentifier);
signalOnNewPublisher(publisherIdentifier);
}
}
/**
* Updates the list of {@link Publisher}s for the topic that this
* {@link Subscriber} is interested in.
*
* @param publisherIdentifiers
* {@link Collection} of {@link PublisherIdentifier}s for the
* subscribed topic
*/
public void updatePublishers(Collection<PublisherIdentifier> publisherIdentifiers) {
for (final PublisherIdentifier publisherIdentifier : publisherIdentifiers) {
executorService.execute(new UpdatePublisherRunnable<T>(this, nodeIdentifier,
publisherIdentifier));
}
}
@Override
public void shutdown(long timeout, TimeUnit unit) {
signalOnShutdown(timeout, unit);
incomingMessageQueue.shutdown();
tcpClientManager.shutdown();
subscriberListeners.shutdown();
}
@Override
public void shutdown() {
shutdown(DEFAULT_SHUTDOWN_TIMEOUT, DEFAULT_SHUTDOWN_TIMEOUT_UNITS);
}
@Override
public void addSubscriberListener(SubscriberListener<T> listener) {
subscriberListeners.add(listener);
}
/**
* Signal all {@link SubscriberListener}s that the {@link Subscriber} has
* successfully registered with the master.
* <p>
* Each listener is called in a separate thread.
*/
@Override
public void signalOnMasterRegistrationSuccess() {
final Subscriber<T> subscriber = this;
subscriberListeners.signal(new SignalRunnable<SubscriberListener<T>>() {
@Override
public void run(SubscriberListener<T> listener) {
listener.onMasterRegistrationSuccess(subscriber);
}
});
}
/**
* Signal all {@link SubscriberListener}s that the {@link Subscriber} has
* failed to register with the master.
*
* <p>
* Each listener is called in a separate thread.
*/
@Override
public void signalOnMasterRegistrationFailure() {
final Subscriber<T> subscriber = this;
subscriberListeners.signal(new SignalRunnable<SubscriberListener<T>>() {
@Override
public void run(SubscriberListener<T> listener) {
listener.onMasterRegistrationFailure(subscriber);
}
});
}
/**
* Signal all {@link SubscriberListener}s that the {@link Subscriber} has
* successfully unregistered with the master.
* <p>
* Each listener is called in a separate thread.
*/
@Override
public void signalOnMasterUnregistrationSuccess() {
final Subscriber<T> subscriber = this;
subscriberListeners.signal(new SignalRunnable<SubscriberListener<T>>() {
@Override
public void run(SubscriberListener<T> listener) {
listener.onMasterUnregistrationSuccess(subscriber);
}
});
}
/**
* Signal all {@link SubscriberListener}s that the {@link Subscriber} has
* failed to unregister with the master.
* <p>
* Each listener is called in a separate thread.
*/
@Override
public void signalOnMasterUnregistrationFailure() {
final Subscriber<T> subscriber = this;
subscriberListeners.signal(new SignalRunnable<SubscriberListener<T>>() {
@Override
public void run(SubscriberListener<T> listener) {
listener.onMasterUnregistrationFailure(subscriber);
}
});
}
/**
* Signal all {@link SubscriberListener}s that a new {@link Publisher} has
* connected.
* <p>
* Each listener is called in a separate thread.
*/
public void signalOnNewPublisher(final PublisherIdentifier publisherIdentifier) {
final Subscriber<T> subscriber = this;
subscriberListeners.signal(new SignalRunnable<SubscriberListener<T>>() {
@Override
public void run(SubscriberListener<T> listener) {
listener.onNewPublisher(subscriber, publisherIdentifier);
}
});
}
/**
* Signal all {@link SubscriberListener}s that the {@link Subscriber} has shut
* down.
* <p>
* Each listener is called in a separate thread.
*/
private void signalOnShutdown(long timeout, TimeUnit unit) {
final Subscriber<T> subscriber = this;
try {
subscriberListeners.signal(new SignalRunnable<SubscriberListener<T>>() {
@Override
public void run(SubscriberListener<T> listener) {
listener.onShutdown(subscriber);
}
}, timeout, unit);
} catch (InterruptedException e) {
// Ignored since we do not guarantee that all listeners will finish
// before
// shutdown begins.
}
}
@Override
public String toString() {
return "Subscriber<" + getTopicDeclaration() + ">";
}
}