001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.hbase.client;
020
021import org.apache.yetus.audience.InterfaceAudience;
022import org.apache.hadoop.hbase.ipc.RpcCallContext;
023import org.apache.hadoop.hbase.ipc.RpcServer;
024import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
025import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
026
027
028/**
029 * Class to help with parsing the version info.
030 */
031@InterfaceAudience.Private
032public final class VersionInfoUtil {
033  private static final ThreadLocal<HBaseProtos.VersionInfo> NonCallVersion = new ThreadLocal<>();
034
035  private VersionInfoUtil() {
036    /* UTIL CLASS ONLY */
037  }
038
039  public static boolean currentClientHasMinimumVersion(int major, int minor) {
040    return hasMinimumVersion(getCurrentClientVersionInfo(), major, minor);
041  }
042
043  public static boolean hasMinimumVersion(HBaseProtos.VersionInfo versionInfo,
044                                          int major,
045                                          int minor) {
046    if (versionInfo != null) {
047      if (versionInfo.hasVersionMajor() && versionInfo.hasVersionMinor()) {
048        int clientMajor = versionInfo.getVersionMajor();
049        if (clientMajor != major) {
050          return clientMajor > major;
051        }
052        int clientMinor = versionInfo.getVersionMinor();
053        return clientMinor >= minor;
054      }
055      try {
056        final String[] components = getVersionComponents(versionInfo);
057
058        int clientMajor = components.length > 0 ? Integer.parseInt(components[0]) : 0;
059        if (clientMajor != major) {
060          return clientMajor > major;
061        }
062
063        int clientMinor = components.length > 1 ? Integer.parseInt(components[1]) : 0;
064        return clientMinor >= minor;
065      } catch (NumberFormatException e) {
066        return false;
067      }
068    }
069    return false;
070  }
071
072  /**
073   *  We intend to use the local version for service call shortcut(s), so we use an interface
074   *  compatible with a typical service call, with 2 args, return type, and an exception type.
075   */
076  public interface ServiceCallFunction<T1, T2, R, E extends Throwable> {
077    R apply(T1 t1, T2 t2) throws E;
078  }
079
080  public static <T1, T2, R, E extends  Throwable> R callWithVersion(
081      ServiceCallFunction<T1, T2, R, E> f, T1 t1, T2 t2) throws E {
082    // Note: just as RpcServer.CurCall, this will only apply on the current thread.
083    NonCallVersion.set(ProtobufUtil.getVersionInfo());
084    try {
085      return f.apply(t1, t2);
086    } finally {
087      NonCallVersion.remove();
088    }
089  }
090
091  /**
092   * @return the versionInfo extracted from the current RpcCallContext
093   */
094  public static HBaseProtos.VersionInfo getCurrentClientVersionInfo() {
095    return RpcServer.getCurrentCall().map(
096        RpcCallContext::getClientVersionInfo).orElse(NonCallVersion.get());
097  }
098
099
100  /**
101   * @param version
102   * @return the passed-in <code>version</code> int as a version String
103   *         (e.g. 0x0103004 is 1.3.4)
104   */
105  public static String versionNumberToString(final int version) {
106    return String.format("%d.%d.%d",
107        ((version >> 20) & 0xff),
108        ((version >> 12) & 0xff),
109        (version & 0xfff));
110  }
111
112  /**
113   * Pack the full number version in a int. by shifting each component by 8bit,
114   * except the dot release which has 12bit.
115   * Examples: (1.3.4 is 0x0103004, 2.1.0 is 0x0201000)
116   * @param versionInfo the VersionInfo object to pack
117   * @return the version number as int. (e.g. 0x0103004 is 1.3.4)
118   */
119  public static int getVersionNumber(final HBaseProtos.VersionInfo versionInfo) {
120    if (versionInfo != null) {
121      try {
122        final String[] components = getVersionComponents(versionInfo);
123        int clientMajor = components.length > 0 ? Integer.parseInt(components[0]) : 0;
124        int clientMinor = components.length > 1 ? Integer.parseInt(components[1]) : 0;
125        int clientPatch = components.length > 2 ? Integer.parseInt(components[2]) : 0;
126        return buildVersionNumber(clientMajor, clientMinor, clientPatch);
127      } catch (NumberFormatException e) {
128        int clientMajor = versionInfo.hasVersionMajor() ? versionInfo.getVersionMajor() : 0;
129        int clientMinor = versionInfo.hasVersionMinor() ? versionInfo.getVersionMinor() : 0;
130        return buildVersionNumber(clientMajor, clientMinor, 0);
131      }
132    }
133    return(0); // no version
134  }
135
136  /**
137   * Pack the full number version in a int. by shifting each component by 8bit,
138   * except the dot release which has 12bit.
139   * Examples: (1.3.4 is 0x0103004, 2.1.0 is 0x0201000)
140   * @param major version major number
141   * @param minor version minor number
142   * @param patch version patch number
143   * @return the version number as int. (e.g. 0x0103004 is 1.3.4)
144   */
145  private static int buildVersionNumber(int major, int minor, int patch) {
146    return (major << 20) | (minor << 12) | patch;
147  }
148
149  /**
150   * Returns the version components
151   * Examples: "1.4.3" returns [1, 4, 3], "4.5.6-SNAPSHOT" returns [4, 5, 6, "SNAPSHOT"]
152   * @return the components of the version string
153   */
154  private static String[] getVersionComponents(final HBaseProtos.VersionInfo versionInfo) {
155    return versionInfo.getVersion().split("[\\.-]");
156  }
157}