blob: fbafabcde4a6d74afe094262e3b9993037af99a8 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.apache.hadoop.ipc;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.apache.hadoop.io.MultipleIOException;
import org.apache.hadoop.ipc.Client.ConnectionId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A utility class used to combine two protocol proxies.
* See {@link #combine(Class, Object...)}.
*/
public final class ProxyCombiner {
private static final Logger LOG =
LoggerFactory.getLogger(ProxyCombiner.class);
private ProxyCombiner() { }
/**
* Combine two or more proxies which together comprise a single proxy
* interface. This can be used for a protocol interface which {@code extends}
* multiple other protocol interfaces. The returned proxy will implement
* all of the methods of the combined proxy interface, delegating calls
* to which proxy implements that method. If multiple proxies implement the
* same method, the first in the list will be used for delegation.
*
* <p/>This will check that every method on the combined interface is
* implemented by at least one of the supplied proxy objects.
*
* @param combinedProxyInterface The interface of the combined proxy.
* @param proxies The proxies which should be used as delegates.
* @param <T> The type of the proxy that will be returned.
* @return The combined proxy.
*/
@SuppressWarnings("unchecked")
public static <T> T combine(Class<T> combinedProxyInterface,
Object... proxies) {
methodLoop:
for (Method m : combinedProxyInterface.getMethods()) {
for (Object proxy : proxies) {
try {
proxy.getClass().getMethod(m.getName(), m.getParameterTypes());
continue methodLoop; // go to the next method
} catch (NoSuchMethodException nsme) {
// Continue to try the next proxy
}
}
throw new IllegalStateException("The proxies specified for "
+ combinedProxyInterface + " do not cover method " + m);
}
InvocationHandler handler = new CombinedProxyInvocationHandler(proxies);
return (T) Proxy.newProxyInstance(combinedProxyInterface.getClassLoader(),
new Class[] {combinedProxyInterface}, handler);
}
private static final class CombinedProxyInvocationHandler
implements RpcInvocationHandler {
private final Object[] proxies;
private CombinedProxyInvocationHandler(Object[] proxies) {
this.proxies = proxies;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Exception lastException = null;
for (Object underlyingProxy : proxies) {
try {
return method.invoke(underlyingProxy, args);
} catch (IllegalAccessException|IllegalArgumentException e) {
lastException = e;
}
}
// This shouldn't happen since the method coverage was verified in build()
LOG.error("BUG: Method {} was unable to be found on any of the "
+ "underlying proxies for {}", method, proxy.getClass());
throw new IllegalArgumentException("Method " + method + " not supported",
lastException);
}
/**
* Since this is incapable of returning multiple connection IDs, simply
* return the first one. In most cases, the connection ID should be the same
* for all proxies.
*/
@Override
public ConnectionId getConnectionId() {
return RPC.getConnectionIdForProxy(proxies[0]);
}
@Override
public void close() throws IOException {
MultipleIOException.Builder exceptionBuilder =
new MultipleIOException.Builder();
for (Object proxy : proxies) {
if (proxy instanceof Closeable) {
try {
((Closeable) proxy).close();
} catch (IOException ioe) {
exceptionBuilder.add(ioe);
}
}
}
if (!exceptionBuilder.isEmpty()) {
throw exceptionBuilder.build();
}
}
}
}