View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.ipc;
20  
21  import java.io.DataInput;
22  import java.io.DataOutput;
23  import java.io.IOException;
24  import java.lang.reflect.Method;
25  import java.util.Arrays;
26  import java.util.HashMap;
27  
28  import org.apache.hadoop.io.Writable;
29  import org.apache.hadoop.io.WritableFactories;
30  import org.apache.hadoop.io.WritableFactory;
31  
32  public class ProtocolSignature implements Writable {
33    static {               // register a ctor
34      WritableFactories.setFactory
35        (ProtocolSignature.class,
36         new WritableFactory() {
37           public Writable newInstance() { return new ProtocolSignature(); }
38         });
39    }
40  
41    private long version;
42    private int[] methods = null; // an array of method hash codes
43  
44    /**
45     * default constructor
46     */
47    public ProtocolSignature() {
48    }
49  
50    /**
51     * Constructor
52     *
53     * @param version server version
54     * @param methodHashcodes hash codes of the methods supported by server
55     */
56    public ProtocolSignature(long version, int[] methodHashcodes) {
57      this.version = version;
58      this.methods = methodHashcodes;
59    }
60  
61    public long getVersion() {
62      return version;
63    }
64  
65    public int[] getMethods() {
66      return methods;
67    }
68  
69    @Override
70    public void readFields(DataInput in) throws IOException {
71      version = in.readLong();
72      boolean hasMethods = in.readBoolean();
73      if (hasMethods) {
74        int numMethods = in.readInt();
75        methods = new int[numMethods];
76        for (int i=0; i<numMethods; i++) {
77          methods[i] = in.readInt();
78        }
79      }
80    }
81  
82    @Override
83    public void write(DataOutput out) throws IOException {
84      out.writeLong(version);
85      if (methods == null) {
86        out.writeBoolean(false);
87      } else {
88        out.writeBoolean(true);
89        out.writeInt(methods.length);
90        for (int method : methods) {
91          out.writeInt(method);
92        }
93      }
94    }
95  
96    /**
97     * Calculate a method's hash code considering its method
98     * name, returning type, and its parameter types
99     *
100    * @param method a method
101    * @return its hash code
102    */
103   static int getFingerprint(Method method) {
104     int hashcode = method.getName().hashCode();
105     hashcode =  hashcode + 31*method.getReturnType().getName().hashCode();
106     for (Class<?> type : method.getParameterTypes()) {
107       hashcode = 31*hashcode ^ type.getName().hashCode();
108     }
109     return hashcode;
110   }
111 
112   /**
113    * Convert an array of Method into an array of hash codes
114    *
115    * @param methods
116    * @return array of hash codes
117    */
118   private static int[] getFingerprints(Method[] methods) {
119     if (methods == null) {
120       return null;
121     }
122     int[] hashCodes = new int[methods.length];
123     for (int i = 0; i<methods.length; i++) {
124       hashCodes[i] = getFingerprint(methods[i]);
125     }
126     return hashCodes;
127   }
128 
129   /**
130    * Get the hash code of an array of methods
131    * Methods are sorted before hashcode is calculated.
132    * So the returned value is irrelevant of the method order in the array.
133    *
134    * @param methods an array of methods
135    * @return the hash code
136    */
137   static int getFingerprint(Method[] methods) {
138     return getFingerprint(getFingerprints(methods));
139   }
140 
141   /**
142    * Get the hash code of an array of hashcodes
143    * Hashcodes are sorted before hashcode is calculated.
144    * So the returned value is irrelevant of the hashcode order in the array.
145    *
146    * @param methods an array of methods
147    * @return the hash code
148    */
149   static int getFingerprint(int[] hashcodes) {
150     Arrays.sort(hashcodes);
151     return Arrays.hashCode(hashcodes);
152 
153   }
154   private static class ProtocolSigFingerprint {
155     private ProtocolSignature signature;
156     private int fingerprint;
157 
158     ProtocolSigFingerprint(ProtocolSignature sig, int fingerprint) {
159       this.signature = sig;
160       this.fingerprint = fingerprint;
161     }
162   }
163 
164   /**
165    * A cache that maps a protocol's name to its signature & finger print
166    */
167   final private static HashMap<String, ProtocolSigFingerprint> 
168      PROTOCOL_FINGERPRINT_CACHE =
169        new HashMap<String, ProtocolSigFingerprint>();
170 
171   /**
172    * Return a protocol's signature and finger print from cache
173    *
174    * @param protocol a protocol class
175    * @param serverVersion protocol version
176    * @return its signature and finger print
177    */
178   private static ProtocolSigFingerprint getSigFingerprint(
179       Class <? extends VersionedProtocol> protocol, long serverVersion) {
180     String protocolName = protocol.getName();
181     synchronized (PROTOCOL_FINGERPRINT_CACHE) {
182       ProtocolSigFingerprint sig = PROTOCOL_FINGERPRINT_CACHE.get(protocolName);
183       if (sig == null) {
184         int[] serverMethodHashcodes = getFingerprints(protocol.getMethods());
185         sig = new ProtocolSigFingerprint(
186             new ProtocolSignature(serverVersion, serverMethodHashcodes),
187             getFingerprint(serverMethodHashcodes));
188         PROTOCOL_FINGERPRINT_CACHE.put(protocolName, sig);
189       }
190       return sig;
191     }
192   }
193 
194   /**
195    * Get a server protocol's signature
196    *
197    * @param clientMethodsHashCode client protocol methods hashcode
198    * @param serverVersion server protocol version
199    * @param protocol protocol
200    * @return the server's protocol signature
201    */
202   static ProtocolSignature getProtocolSignature(
203       int clientMethodsHashCode,
204       long serverVersion,
205       Class<? extends VersionedProtocol> protocol) {
206     // try to get the finger print & signature from the cache
207     ProtocolSigFingerprint sig = getSigFingerprint(protocol, serverVersion);
208 
209     // check if the client side protocol matches the one on the server side
210     if (clientMethodsHashCode == sig.fingerprint) {
211       return new ProtocolSignature(serverVersion, null);  // null indicates a match
212     }
213 
214     return sig.signature;
215   }
216 
217   /**
218    * Get a server protocol's signature
219    *
220    * @param server server implementation
221    * @param protocol server protocol
222    * @param clientVersion client's version
223    * @param clientMethodsHash client's protocol's hash code
224    * @return the server protocol's signature
225    * @throws IOException if any error occurs
226    */
227   @SuppressWarnings("unchecked")
228   public static ProtocolSignature getProtocolSignature(VersionedProtocol server,
229       String protocol,
230       long clientVersion, int clientMethodsHash) throws IOException {
231     Class<? extends VersionedProtocol> inter;
232     try {
233       inter = (Class<? extends VersionedProtocol>)Class.forName(protocol);
234     } catch (Exception e) {
235       throw new IOException(e);
236     }
237     long serverVersion = server.getProtocolVersion(protocol, clientVersion);
238     return ProtocolSignature.getProtocolSignature(
239         clientMethodsHash, serverVersion, inter);
240   }
241 }