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  /** Returns the versionInfo extracted from the current RpcCallContext */
090  public static HBaseProtos.VersionInfo getCurrentClientVersionInfo() {
091    return RpcServer.getCurrentCall().map(RpcCallContext::getClientVersionInfo)
092      .orElse(NonCallVersion.get());
093  }
094
095  /**
096   * Returns the passed-in <code>version</code> int as a version String (e.g. 0x0103004 is 1.3.4)
097   */
098  public static String versionNumberToString(final int version) {
099    return String.format("%d.%d.%d", ((version >> 20) & 0xff), ((version >> 12) & 0xff),
100      (version & 0xfff));
101  }
102
103  /**
104   * Pack the full number version in a int. by shifting each component by 8bit, except the dot
105   * release which has 12bit. Examples: (1.3.4 is 0x0103004, 2.1.0 is 0x0201000)
106   * @param versionInfo the VersionInfo object to pack
107   * @return the version number as int. (e.g. 0x0103004 is 1.3.4)
108   */
109  public static int getVersionNumber(final HBaseProtos.VersionInfo versionInfo) {
110    if (versionInfo != null) {
111      try {
112        final String[] components = getVersionComponents(versionInfo);
113        int clientMajor = components.length > 0 ? Integer.parseInt(components[0]) : 0;
114        int clientMinor = components.length > 1 ? Integer.parseInt(components[1]) : 0;
115        int clientPatch = components.length > 2 ? Integer.parseInt(components[2]) : 0;
116        return buildVersionNumber(clientMajor, clientMinor, clientPatch);
117      } catch (NumberFormatException e) {
118        int clientMajor = versionInfo.hasVersionMajor() ? versionInfo.getVersionMajor() : 0;
119        int clientMinor = versionInfo.hasVersionMinor() ? versionInfo.getVersionMinor() : 0;
120        return buildVersionNumber(clientMajor, clientMinor, 0);
121      }
122    }
123    return (0); // no version
124  }
125
126  /**
127   * Pack the full number version in a int. by shifting each component by 8bit, except the dot
128   * release which has 12bit. Examples: (1.3.4 is 0x0103004, 2.1.0 is 0x0201000)
129   * @param major version major number
130   * @param minor version minor number
131   * @param patch version patch number
132   * @return the version number as int. (e.g. 0x0103004 is 1.3.4)
133   */
134  private static int buildVersionNumber(int major, int minor, int patch) {
135    return (major << 20) | (minor << 12) | patch;
136  }
137
138  /**
139   * Returns the version components Examples: "1.4.3" returns [1, 4, 3], "4.5.6-SNAPSHOT" returns
140   * [4, 5, 6, "SNAPSHOT"]
141   * @return the components of the version string
142   */
143  private static String[] getVersionComponents(final HBaseProtos.VersionInfo versionInfo) {
144    return versionInfo.getVersion().split("[\\.-]");
145  }
146}