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}