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.ipc;
019
020import java.util.concurrent.ConcurrentHashMap;
021import org.slf4j.Logger;
022import org.slf4j.LoggerFactory;
023
024final class ShadedPrefixUtil {
025  private static final Logger LOG = LoggerFactory.getLogger(ShadedPrefixUtil.class);
026
027  // Marked with '!' to prevent shading tools from replacing this value
028  static final String ORIGINAL_BASE = "org!apache!hadoop!hbase".replace('!', '.');
029
030  private static final ShadedPrefixUtil INSTANCE =
031    newInstance(ShadedPrefixUtil.class.getPackage().getName());
032
033  /**
034   * The shaded equivalent of "org.apache.hadoop.hbase", or null if not in a shaded environment.
035   * <ul>
036   * <li>Prefix-prepend shading (e.g., current package "a.b.c.org.apache.hadoop.hbase.ipc"):
037   * "a.b.c.org.apache.hadoop.hbase"</li>
038   * <li>Full-replacement shading (e.g., current package "shaded.hbase1.ipc"): "shaded.hbase1"</li>
039   * </ul>
040   */
041  private final String shadedBase;
042
043  /**
044   * Cache from original class name to its resolved (shaded or original) equivalent.
045   */
046  private final ConcurrentHashMap<String, String> resolvedClassNames = new ConcurrentHashMap<>();
047
048  private ShadedPrefixUtil(String shadedBase) {
049    this.shadedBase = shadedBase;
050  }
051
052  static ShadedPrefixUtil newInstance(String currentPackageName) {
053    // The ipc suffix identifies which sub-package this class lives in
054    final String ipcSuffix = ".ipc";
055    final String originalPackage = ORIGINAL_BASE + ipcSuffix;
056
057    if (currentPackageName == null || currentPackageName.equals(originalPackage)) {
058      LOG.debug("{} is not a shaded package", currentPackageName);
059      return new ShadedPrefixUtil(null);
060    }
061
062    if (!currentPackageName.endsWith(ipcSuffix)) {
063      LOG.debug("Cannot determine shading mapping for {}", currentPackageName);
064      return new ShadedPrefixUtil(null);
065    }
066
067    // Works for both prefix-prepend and full-replacement shading strategies:
068    // - "a.b.c.org.apache.hadoop.hbase.ipc" -> shadedBase = "a.b.c.org.apache.hadoop.hbase"
069    // - "shaded.hbase1.ipc" -> shadedBase = "shaded.hbase1"
070    String detectedShadedBase =
071      currentPackageName.substring(0, currentPackageName.length() - ipcSuffix.length());
072    LOG.debug("{} is a shaded package, shadedBase={}", currentPackageName, detectedShadedBase);
073    return new ShadedPrefixUtil(detectedShadedBase);
074  }
075
076  String getShadedBase() {
077    return shadedBase;
078  }
079
080  /**
081   * Maps an original HBase class name to its shaded equivalent by replacing the
082   * "org.apache.hadoop.hbase" prefix with the detected shaded base package. Returns the original
083   * class name unchanged if not in a shaded environment or if the class name does not start with
084   * the original HBase package. Note: this is a pure string transformation with no class loading.
085   * Use {@link #resolveShading(String)} when class existence must be verified.
086   */
087  String applyShading(String className) {
088    if (shadedBase == null || className == null) {
089      return className;
090    }
091    if (!className.startsWith(ORIGINAL_BASE + ".")) {
092      return className;
093    }
094    return shadedBase + className.substring(ORIGINAL_BASE.length());
095  }
096
097  /**
098   * Returns the effective class name for the given original HBase class name. Prefers the shaded
099   * class name when it can be loaded, and falls back to the original when the shaded class is not
100   * available (e.g., when exception classes are intentionally excluded from shading). The result is
101   * cached so that Class.forName is invoked at most once per distinct class name.
102   */
103  String resolveShading(String originalName) {
104    if (shadedBase == null || originalName == null) {
105      return originalName;
106    }
107    return resolvedClassNames.computeIfAbsent(originalName, this::computeResolvedName);
108  }
109
110  private String computeResolvedName(String originalName) {
111    String shadedName = applyShading(originalName);
112    if (shadedName.equals(originalName)) {
113      return originalName;
114    }
115    try {
116      Class.forName(shadedName, false, ShadedPrefixUtil.class.getClassLoader());
117      return shadedName;
118    } catch (Throwable t) {
119      LOG.debug("Shaded class {} not found, falling back to {}", shadedName, originalName);
120      return originalName;
121    }
122  }
123
124  public static ShadedPrefixUtil getInstance() {
125    return INSTANCE;
126  }
127}