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 */
018package org.apache.hadoop.hbase.client;
019
020import org.apache.hadoop.hbase.ipc.RpcCallContext;
021import org.apache.hadoop.hbase.ipc.RpcServer;
022import org.apache.yetus.audience.InterfaceAudience;
023
024import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
025import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
026
027/**
028 * Class to help with parsing the version info.
029 */
030@InterfaceAudience.Private
031public final class VersionInfoUtil {
032  private static final ThreadLocal<HBaseProtos.VersionInfo> NonCallVersion = new ThreadLocal<>();
033
034  private VersionInfoUtil() {
035    /* UTIL CLASS ONLY */
036  }
037
038  public static boolean currentClientHasMinimumVersion(int major, int minor) {
039    return hasMinimumVersion(getCurrentClientVersionInfo(), major, minor);
040  }
041
042  public static boolean hasMinimumVersion(HBaseProtos.VersionInfo versionInfo, int major,
043    int minor) {
044    if (versionInfo != null) {
045      if (versionInfo.hasVersionMajor() && versionInfo.hasVersionMinor()) {
046        int clientMajor = versionInfo.getVersionMajor();
047        if (clientMajor != major) {
048          return clientMajor > major;
049        }
050        int clientMinor = versionInfo.getVersionMinor();
051        return clientMinor >= minor;
052      }
053      try {
054        final String[] components = getVersionComponents(versionInfo);
055
056        int clientMajor = components.length > 0 ? Integer.parseInt(components[0]) : 0;
057        if (clientMajor != major) {
058          return clientMajor > major;
059        }
060
061        int clientMinor = components.length > 1 ? Integer.parseInt(components[1]) : 0;
062        return clientMinor >= minor;
063      } catch (NumberFormatException e) {
064        return false;
065      }
066    }
067    return false;
068  }
069
070  /**
071   * We intend to use the local version for service call shortcut(s), so we use an interface
072   * compatible with a typical service call, with 2 args, return type, and an exception type.
073   */
074  public interface ServiceCallFunction<T1, T2, R, E extends Throwable> {
075    R apply(T1 t1, T2 t2) throws E;
076  }
077
078  public static <T1, T2, R, E extends Throwable> R
079    callWithVersion(ServiceCallFunction<T1, T2, R, E> f, T1 t1, T2 t2) throws E {
080    // Note: just as RpcServer.CurCall, this will only apply on the current thread.
081    NonCallVersion.set(ProtobufUtil.getVersionInfo());
082    try {
083      return f.apply(t1, t2);
084    } finally {
085      NonCallVersion.remove();
086    }
087  }
088
089  /**
090   * @return the versionInfo extracted from the current RpcCallContext
091   */
092  public static HBaseProtos.VersionInfo getCurrentClientVersionInfo() {
093    return RpcServer.getCurrentCall().map(RpcCallContext::getClientVersionInfo)
094      .orElse(NonCallVersion.get());
095  }
096
097  /**
098   * n * @return the passed-in <code>version</code> int as a version String (e.g. 0x0103004 is
099   * 1.3.4)
100   */
101  public static String versionNumberToString(final int version) {
102    return String.format("%d.%d.%d", ((version >> 20) & 0xff), ((version >> 12) & 0xff),
103      (version & 0xfff));
104  }
105
106  /**
107   * Pack the full number version in a int. by shifting each component by 8bit, except the dot
108   * release which has 12bit. Examples: (1.3.4 is 0x0103004, 2.1.0 is 0x0201000)
109   * @param versionInfo the VersionInfo object to pack
110   * @return the version number as int. (e.g. 0x0103004 is 1.3.4)
111   */
112  public static int getVersionNumber(final HBaseProtos.VersionInfo versionInfo) {
113    if (versionInfo != null) {
114      try {
115        final String[] components = getVersionComponents(versionInfo);
116        int clientMajor = components.length > 0 ? Integer.parseInt(components[0]) : 0;
117        int clientMinor = components.length > 1 ? Integer.parseInt(components[1]) : 0;
118        int clientPatch = components.length > 2 ? Integer.parseInt(components[2]) : 0;
119        return buildVersionNumber(clientMajor, clientMinor, clientPatch);
120      } catch (NumberFormatException e) {
121        int clientMajor = versionInfo.hasVersionMajor() ? versionInfo.getVersionMajor() : 0;
122        int clientMinor = versionInfo.hasVersionMinor() ? versionInfo.getVersionMinor() : 0;
123        return buildVersionNumber(clientMajor, clientMinor, 0);
124      }
125    }
126    return (0); // no version
127  }
128
129  /**
130   * Pack the full number version in a int. by shifting each component by 8bit, except the dot
131   * release which has 12bit. Examples: (1.3.4 is 0x0103004, 2.1.0 is 0x0201000)
132   * @param major version major number
133   * @param minor version minor number
134   * @param patch version patch number
135   * @return the version number as int. (e.g. 0x0103004 is 1.3.4)
136   */
137  private static int buildVersionNumber(int major, int minor, int patch) {
138    return (major << 20) | (minor << 12) | patch;
139  }
140
141  /**
142   * Returns the version components Examples: "1.4.3" returns [1, 4, 3], "4.5.6-SNAPSHOT" returns
143   * [4, 5, 6, "SNAPSHOT"]
144   * @return the components of the version string
145   */
146  private static String[] getVersionComponents(final HBaseProtos.VersionInfo versionInfo) {
147    return versionInfo.getVersion().split("[\\.-]");
148  }
149}