/*
* Copyright (C) 2012 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.master.uri;
import com.google.common.collect.Lists;
import org.ros.exception.RosRuntimeException;
import java.net.URI;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* A proxying {@link MasterUriProvider} which can be switched between providers.
*
* <p>
* This class is thread-safe.
*
* @author Keith M. Hughes
*/
public class SwitchableMasterUriProvider implements MasterUriProvider {
private final Object mutex;
/**
* The current provider in use.
*/
private MasterUriProvider provider;
/**
* The list of all pending requests.
*/
private List<ProviderRequest> pending = Lists.newArrayList();
/**
* @param provider
* the initial provider to use
*/
public SwitchableMasterUriProvider(MasterUriProvider provider) {
this.provider = provider;
mutex = new Object();
}
@Override
public URI getMasterUri() throws RosRuntimeException {
MasterUriProvider providerToUse = null;
ProviderRequest requestToUse = null;
synchronized (mutex) {
if (provider != null) {
providerToUse = provider;
} else {
requestToUse = new ProviderRequest();
pending.add(requestToUse);
}
}
if (providerToUse != null) {
return providerToUse.getMasterUri();
} else {
return requestToUse.getMasterUri();
}
}
@Override
public URI getMasterUri(long timeout, TimeUnit unit) {
// We can't really switch providers, but people are willing to wait. It
// seems appropriate to wait rather than to return immediately.
MasterUriProvider providerToUse = null;
synchronized (mutex) {
if (provider != null) {
providerToUse = provider;
}
}
if (providerToUse != null) {
return providerToUse.getMasterUri(timeout, unit);
} else {
try {
Thread.sleep(unit.toMillis(timeout));
} catch (InterruptedException e) {
// Don't care
}
return null;
}
}
/**
* Switch between providers.
*
* @param switcher
* the new provider
*/
public void switchProvider(MasterUriProviderSwitcher switcher) {
synchronized (mutex) {
MasterUriProvider oldProvider = provider;
provider = switcher.switchProvider(oldProvider);
if (oldProvider == null) {
for (ProviderRequest request : pending) {
request.setProvider(provider);
}
pending.clear();
}
}
}
/**
* Perform a switch between {@link MasterUriProvider} instances for the
* {@link SwitchableMasterUriProvider}.
*
* <p>
* This class permits the use of atomic provider switches.
*/
public interface MasterUriProviderSwitcher {
/**
* Switch the provider in use.
*
* @param oldProvider
* a reference to the provider which came before
*
* @return the new provider to use
*/
MasterUriProvider switchProvider(MasterUriProvider oldProvider);
}
/**
* A request for a URI which is blocked until it is available.
*/
private static class ProviderRequest {
/**
* The latch used to wait.
*/
private CountDownLatch latch = new CountDownLatch(1);
/**
* The provider which will give the eventual answer.
*/
private MasterUriProvider provider;
/**
* Get a service.
*
* <p>
* This call can block indefinitely.
*
* @return the master {@link URI}
*/
public URI getMasterUri() {
try {
latch.await();
return provider.getMasterUri();
} catch (InterruptedException e) {
throw new RosRuntimeException("URI provider interrupted", e);
}
}
/**
* Set the provider who will finally process the request.
*
* @param provider
* the {@link MasterUriProvider} to use
*/
public void setProvider(MasterUriProvider provider) {
this.provider = provider;
latch.countDown();
}
}
}