| /** |
| * 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 |
| * |
| * 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.apache.hadoop.ipc; |
| |
| import java.io.DataInput; |
| import java.io.DataOutput; |
| import java.io.IOException; |
| import java.lang.reflect.Method; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| |
| import org.apache.hadoop.io.Writable; |
| import org.apache.hadoop.io.WritableFactories; |
| import org.apache.hadoop.io.WritableFactory; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| |
| public class ProtocolSignature implements Writable { |
| static { // register a ctor |
| WritableFactories.setFactory |
| (ProtocolSignature.class, |
| new WritableFactory() { |
| @Override |
| public Writable newInstance() { return new ProtocolSignature(); } |
| }); |
| } |
| |
| private long version; |
| private int[] methods = null; // an array of method hash codes |
| |
| /** |
| * default constructor |
| */ |
| public ProtocolSignature() { |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param version server version |
| * @param methodHashcodes hash codes of the methods supported by server |
| */ |
| public ProtocolSignature(long version, int[] methodHashcodes) { |
| this.version = version; |
| this.methods = methodHashcodes; |
| } |
| |
| public long getVersion() { |
| return version; |
| } |
| |
| public int[] getMethods() { |
| return methods; |
| } |
| |
| @Override |
| public void readFields(DataInput in) throws IOException { |
| version = in.readLong(); |
| boolean hasMethods = in.readBoolean(); |
| if (hasMethods) { |
| int numMethods = in.readInt(); |
| methods = new int[numMethods]; |
| for (int i=0; i<numMethods; i++) { |
| methods[i] = in.readInt(); |
| } |
| } |
| } |
| |
| @Override |
| public void write(DataOutput out) throws IOException { |
| out.writeLong(version); |
| if (methods == null) { |
| out.writeBoolean(false); |
| } else { |
| out.writeBoolean(true); |
| out.writeInt(methods.length); |
| for (int method : methods) { |
| out.writeInt(method); |
| } |
| } |
| } |
| |
| /** |
| * Calculate a method's hash code considering its method |
| * name, returning type, and its parameter types |
| * |
| * @param method a method |
| * @return its hash code |
| */ |
| static int getFingerprint(Method method) { |
| int hashcode = method.getName().hashCode(); |
| hashcode = hashcode + 31*method.getReturnType().getName().hashCode(); |
| for (Class<?> type : method.getParameterTypes()) { |
| hashcode = 31*hashcode ^ type.getName().hashCode(); |
| } |
| return hashcode; |
| } |
| |
| /** |
| * Convert an array of Method into an array of hash codes |
| * |
| * @param methods |
| * @return array of hash codes |
| */ |
| private static int[] getFingerprints(Method[] methods) { |
| if (methods == null) { |
| return null; |
| } |
| int[] hashCodes = new int[methods.length]; |
| for (int i = 0; i<methods.length; i++) { |
| hashCodes[i] = getFingerprint(methods[i]); |
| } |
| return hashCodes; |
| } |
| |
| /** |
| * Get the hash code of an array of methods |
| * Methods are sorted before hashcode is calculated. |
| * So the returned value is irrelevant of the method order in the array. |
| * |
| * @param methods an array of methods |
| * @return the hash code |
| */ |
| static int getFingerprint(Method[] methods) { |
| return getFingerprint(getFingerprints(methods)); |
| } |
| |
| /** |
| * Get the hash code of an array of hashcodes |
| * Hashcodes are sorted before hashcode is calculated. |
| * So the returned value is irrelevant of the hashcode order in the array. |
| * |
| * @param methods an array of methods |
| * @return the hash code |
| */ |
| static int getFingerprint(int[] hashcodes) { |
| Arrays.sort(hashcodes); |
| return Arrays.hashCode(hashcodes); |
| |
| } |
| private static class ProtocolSigFingerprint { |
| private ProtocolSignature signature; |
| private int fingerprint; |
| |
| ProtocolSigFingerprint(ProtocolSignature sig, int fingerprint) { |
| this.signature = sig; |
| this.fingerprint = fingerprint; |
| } |
| } |
| |
| /** |
| * A cache that maps a protocol's name to its signature & finger print |
| */ |
| private final static HashMap<String, ProtocolSigFingerprint> |
| PROTOCOL_FINGERPRINT_CACHE = |
| new HashMap<String, ProtocolSigFingerprint>(); |
| |
| @VisibleForTesting |
| public static void resetCache() { |
| PROTOCOL_FINGERPRINT_CACHE.clear(); |
| } |
| |
| /** |
| * Return a protocol's signature and finger print from cache |
| * |
| * @param protocol a protocol class |
| * @param serverVersion protocol version |
| * @return its signature and finger print |
| */ |
| private static ProtocolSigFingerprint getSigFingerprint( |
| Class <?> protocol, long serverVersion) { |
| String protocolName = RPC.getProtocolName(protocol); |
| synchronized (PROTOCOL_FINGERPRINT_CACHE) { |
| ProtocolSigFingerprint sig = PROTOCOL_FINGERPRINT_CACHE.get(protocolName); |
| if (sig == null) { |
| int[] serverMethodHashcodes = getFingerprints(protocol.getMethods()); |
| sig = new ProtocolSigFingerprint( |
| new ProtocolSignature(serverVersion, serverMethodHashcodes), |
| getFingerprint(serverMethodHashcodes)); |
| PROTOCOL_FINGERPRINT_CACHE.put(protocolName, sig); |
| } |
| return sig; |
| } |
| } |
| |
| /** |
| * Get a server protocol's signature |
| * |
| * @param clientMethodsHashCode client protocol methods hashcode |
| * @param serverVersion server protocol version |
| * @param protocol protocol |
| * @return the server's protocol signature |
| */ |
| public static ProtocolSignature getProtocolSignature( |
| int clientMethodsHashCode, |
| long serverVersion, |
| Class<? extends VersionedProtocol> protocol) { |
| // try to get the finger print & signature from the cache |
| ProtocolSigFingerprint sig = getSigFingerprint(protocol, serverVersion); |
| |
| // check if the client side protocol matches the one on the server side |
| if (clientMethodsHashCode == sig.fingerprint) { |
| return new ProtocolSignature(serverVersion, null); // null indicates a match |
| } |
| |
| return sig.signature; |
| } |
| |
| public static ProtocolSignature getProtocolSignature(String protocolName, |
| long version) throws ClassNotFoundException { |
| Class<?> protocol = Class.forName(protocolName); |
| return getSigFingerprint(protocol, version).signature; |
| } |
| |
| /** |
| * Get a server protocol's signature |
| * |
| * @param server server implementation |
| * @param protocol server protocol |
| * @param clientVersion client's version |
| * @param clientMethodsHash client's protocol's hash code |
| * @return the server protocol's signature |
| * @throws IOException if any error occurs |
| */ |
| @SuppressWarnings("unchecked") |
| public static ProtocolSignature getProtocolSignature(VersionedProtocol server, |
| String protocol, |
| long clientVersion, int clientMethodsHash) throws IOException { |
| Class<? extends VersionedProtocol> inter; |
| try { |
| inter = (Class<? extends VersionedProtocol>)Class.forName(protocol); |
| } catch (Exception e) { |
| throw new IOException(e); |
| } |
| long serverVersion = server.getProtocolVersion(protocol, clientVersion); |
| return ProtocolSignature.getProtocolSignature( |
| clientMethodsHash, serverVersion, inter); |
| } |
| } |