/*
* 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.client;
import com.google.common.base.Preconditions;
import org.apache.commons.logging.Log;
import org.ros.concurrent.Holder;
import org.ros.concurrent.RetryingExecutorService;
import org.ros.exception.RosRuntimeException;
import org.ros.internal.node.response.Response;
import org.ros.internal.node.server.NodeIdentifier;
import org.ros.internal.node.server.SlaveServer;
import org.ros.internal.node.server.master.MasterServer;
import org.ros.internal.node.service.DefaultServiceServer;
import org.ros.internal.node.service.ServiceManagerListener;
import org.ros.internal.node.topic.DefaultPublisher;
import org.ros.internal.node.topic.DefaultSubscriber;
import org.ros.internal.node.topic.PublisherIdentifier;
import org.ros.internal.node.topic.TopicParticipantManagerListener;
import org.ros.log.RosLogFactory;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Manages topic, and service registrations of a {@link SlaveServer} with the
* {@link MasterServer}.
*
* @author kwc@willowgarage.com (Ken Conley)
* @author damonkohler@google.com (Damon Kohler)
*/
public class Registrar implements TopicParticipantManagerListener, ServiceManagerListener {
private static final Log log = RosLogFactory.getLog(Registrar.class);
private static final int SHUTDOWN_TIMEOUT = 5;
private static final TimeUnit SHUTDOWN_TIMEOUT_UNITS = TimeUnit.SECONDS;
private final MasterClient masterClient;
private final ScheduledExecutorService executorService;
private final RetryingExecutorService retryingExecutorService;
private NodeIdentifier nodeIdentifier;
private boolean running;
/**
* @param masterClient
* a {@link MasterClient} for communicating with the ROS master
* @param executorService
* a {@link ScheduledExecutorService} to be used for all asynchronous
* operations
*/
public Registrar(MasterClient masterClient, ScheduledExecutorService executorService) {
this.masterClient = masterClient;
this.executorService = executorService;
retryingExecutorService = new RetryingExecutorService(executorService);
nodeIdentifier = null;
running = false;
if (log.isDebugEnabled()) {
log.debug("MasterXmlRpcEndpoint URI: " + masterClient.getRemoteUri());
}
}
/**
* Failed registration actions are retried periodically until they succeed.
* This method adjusts the delay between successive retry attempts for any
* particular registration action.
*
* @param delay
* the delay in units of {@code unit} between retries
* @param unit
* the unit of {@code delay}
*/
public void setRetryDelay(long delay, TimeUnit unit) {
retryingExecutorService.setRetryDelay(delay, unit);
}
private boolean submit(Callable<Boolean> callable) {
if (running) {
retryingExecutorService.submit(callable);
return true;
}
log.warn("Registrar no longer running, request ignored.");
return false;
}
private <T> boolean callMaster(Callable<Response<T>> callable) {
Preconditions.checkNotNull(nodeIdentifier, "Registrar not started.");
boolean success;
try {
Response<T> response = callable.call();
if (log.isDebugEnabled()) {
log.debug(response);
}
success = response.isSuccess();
} catch (Exception e) {
log.error("Exception caught while communicating with master.", e);
success = false;
}
return success;
}
@Override
public void onPublisherAdded(final DefaultPublisher<?> publisher) {
if (log.isDebugEnabled()) {
log.debug("Registering publisher: " + publisher);
}
boolean submitted = submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
boolean success = callMaster(new Callable<Response<List<URI>>>() {
@Override
public Response<List<URI>> call() throws Exception {
return masterClient.registerPublisher(publisher.toDeclaration());
}
});
if (success) {
publisher.signalOnMasterRegistrationSuccess();
} else {
publisher.signalOnMasterRegistrationFailure();
}
return !success;
}
});
if (!submitted) {
executorService.execute(new Runnable() {
@Override
public void run() {
publisher.signalOnMasterRegistrationFailure();
}
});
}
}
@Override
public void onPublisherRemoved(final DefaultPublisher<?> publisher) {
if (log.isDebugEnabled()) {
log.debug("Unregistering publisher: " + publisher);
}
boolean submitted = submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
boolean success = callMaster(new Callable<Response<Integer>>() {
@Override
public Response<Integer> call() throws Exception {
return masterClient.unregisterPublisher(publisher.getIdentifier());
}
});
if (success) {
publisher.signalOnMasterUnregistrationSuccess();
} else {
publisher.signalOnMasterUnregistrationFailure();
}
return !success;
}
});
if (!submitted) {
executorService.execute(new Runnable() {
@Override
public void run() {
publisher.signalOnMasterUnregistrationFailure();
}
});
}
}
@Override
public void onSubscriberAdded(final DefaultSubscriber<?> subscriber) {
if (log.isDebugEnabled()) {
log.debug("Registering subscriber: " + subscriber);
}
boolean submitted = submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
final Holder<Response<List<URI>>> holder = Holder.newEmpty();
boolean success = callMaster(new Callable<Response<List<URI>>>() {
@Override
public Response<List<URI>> call() throws Exception {
return holder.set(masterClient.registerSubscriber(nodeIdentifier, subscriber));
}
});
if (success) {
Collection<PublisherIdentifier> publisherIdentifiers =
PublisherIdentifier.newCollectionFromUris(holder.get().getResult(),
subscriber.getTopicDeclaration());
subscriber.updatePublishers(publisherIdentifiers);
subscriber.signalOnMasterRegistrationSuccess();
} else {
subscriber.signalOnMasterRegistrationFailure();
}
return !success;
}
});
if (!submitted) {
executorService.execute(new Runnable() {
@Override
public void run() {
subscriber.signalOnMasterRegistrationFailure();
}
});
}
}
@Override
public void onSubscriberRemoved(final DefaultSubscriber<?> subscriber) {
if (log.isDebugEnabled()) {
log.debug("Unregistering subscriber: " + subscriber);
}
boolean submitted = submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
boolean success = callMaster(new Callable<Response<Integer>>() {
@Override
public Response<Integer> call() throws Exception {
return masterClient.unregisterSubscriber(nodeIdentifier, subscriber);
}
});
if (success) {
subscriber.signalOnMasterUnregistrationSuccess();
} else {
subscriber.signalOnMasterUnregistrationFailure();
}
return !success;
}
});
if (!submitted) {
log.warn("Master subscriber unregistration never submitted");
executorService.execute(new Runnable() {
@Override
public void run() {
subscriber.signalOnMasterUnregistrationFailure();
}
});
}
}
@Override
public void onServiceServerAdded(final DefaultServiceServer<?, ?> serviceServer) {
if (log.isDebugEnabled()) {
log.debug("Registering service: " + serviceServer);
}
boolean submitted = submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
boolean success = callMaster(new Callable<Response<Void>>() {
@Override
public Response<Void> call() throws Exception {
return masterClient.registerService(nodeIdentifier, serviceServer);
}
});
if (success) {
serviceServer.signalOnMasterRegistrationSuccess();
} else {
serviceServer.signalOnMasterRegistrationFailure();
}
return !success;
}
});
if (!submitted) {
executorService.execute(new Runnable() {
@Override
public void run() {
serviceServer.signalOnMasterRegistrationFailure();
}
});
}
}
@Override
public void onServiceServerRemoved(final DefaultServiceServer<?, ?> serviceServer) {
if (log.isDebugEnabled()) {
log.debug("Unregistering service: " + serviceServer);
}
boolean submitted = submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
boolean success = callMaster(new Callable<Response<Integer>>() {
@Override
public Response<Integer> call() throws Exception {
return masterClient.unregisterService(nodeIdentifier, serviceServer);
}
});
if (success) {
serviceServer.signalOnMasterUnregistrationSuccess();
} else {
serviceServer.signalOnMasterUnregistrationFailure();
}
return !success;
}
});
if (!submitted) {
executorService.execute(new Runnable() {
@Override
public void run() {
serviceServer.signalOnMasterUnregistrationFailure();
}
});
}
}
/**
* Starts the {@link Registrar} for the {@link SlaveServer} identified by the
* given {@link NodeIdentifier}.
*
* @param nodeIdentifier
* the {@link NodeIdentifier} for the {@link SlaveServer} this
* {@link Registrar} is responsible for
*/
public void start(NodeIdentifier nodeIdentifier) {
Preconditions.checkNotNull(nodeIdentifier);
Preconditions.checkState(this.nodeIdentifier == null, "Registrar already started.");
this.nodeIdentifier = nodeIdentifier;
running = true;
}
/**
* Shuts down the {@link Registrar}.
*
* <p>
* No further registration requests will be accepted. All queued registration
* jobs have up to {@link #SHUTDOWN_TIMEOUT} {@link #SHUTDOWN_TIMEOUT_UNITS}
* to complete before being canceled.
*
* <p>
* Calling {@link #shutdown()} more than once has no effect.
*/
public void shutdown() {
if (!running) {
return;
}
running = false;
try {
retryingExecutorService.shutdown(SHUTDOWN_TIMEOUT, SHUTDOWN_TIMEOUT_UNITS);
} catch (InterruptedException e) {
throw new RosRuntimeException(e);
}
}
}