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.util; 019 020import static org.junit.Assert.assertFalse; 021import static org.junit.Assert.assertTrue; 022import static org.junit.Assert.fail; 023 024import java.io.BufferedWriter; 025import java.io.File; 026import java.io.FileInputStream; 027import java.io.FileOutputStream; 028import java.nio.charset.StandardCharsets; 029import java.nio.file.Files; 030import java.util.ArrayList; 031import java.util.List; 032import java.util.jar.JarEntry; 033import java.util.jar.JarOutputStream; 034import java.util.jar.Manifest; 035import javax.tools.JavaCompiler; 036import javax.tools.JavaFileObject; 037import javax.tools.StandardJavaFileManager; 038import javax.tools.ToolProvider; 039import org.apache.hadoop.conf.Configuration; 040import org.apache.hadoop.fs.Path; 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043 044/** 045 * Some utilities to help class loader testing 046 */ 047public final class ClassLoaderTestHelper { 048 private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderTestHelper.class); 049 050 private static final int BUFFER_SIZE = 4096; 051 052 private ClassLoaderTestHelper() { 053 } 054 055 /** 056 * Jar a list of files into a jar archive. 057 * @param archiveFile the target jar archive 058 * @param tobeJared a list of files to be jared 059 * @return true if a jar archive is build, false otherwise 060 */ 061 private static boolean createJarArchive(File archiveFile, File[] tobeJared) { 062 try { 063 byte[] buffer = new byte[BUFFER_SIZE]; 064 // Open archive file 065 FileOutputStream stream = new FileOutputStream(archiveFile); 066 JarOutputStream out = new JarOutputStream(stream, new Manifest()); 067 068 for (File file : tobeJared) { 069 if (file == null || !file.exists() || file.isDirectory()) { 070 continue; 071 } 072 073 // Add archive entry 074 JarEntry jarAdd = new JarEntry(file.getName()); 075 jarAdd.setTime(file.lastModified()); 076 out.putNextEntry(jarAdd); 077 078 // Write file to archive 079 FileInputStream in = new FileInputStream(file); 080 while (true) { 081 int nRead = in.read(buffer, 0, buffer.length); 082 if (nRead <= 0) { 083 break; 084 } 085 086 out.write(buffer, 0, nRead); 087 } 088 in.close(); 089 } 090 out.close(); 091 stream.close(); 092 LOG.info("Adding classes to jar file completed"); 093 return true; 094 } catch (Exception ex) { 095 LOG.error("Error: " + ex.getMessage()); 096 return false; 097 } 098 } 099 100 /** 101 * Create a test jar for testing purpose for a given class name with specified code string: save 102 * the class to a file, compile it, and jar it up. If the code string passed in is null, a bare 103 * empty class will be created and used. 104 * @param testDir the folder under which to store the test class and jar 105 * @param className the test class name 106 * @param code the optional test class code, which can be null. If null, a bare empty class 107 * will be used 108 * @return the test jar file generated 109 */ 110 public static File buildJar(String testDir, String className, String code) throws Exception { 111 return buildJar(testDir, className, code, testDir); 112 } 113 114 /** 115 * Create a test jar for testing purpose for a given class name with specified code string. 116 * @param testDir the folder under which to store the test class 117 * @param className the test class name 118 * @param code the optional test class code, which can be null. If null, an empty class will 119 * be used 120 * @param folder the folder under which to store the generated jar 121 * @return the test jar file generated 122 */ 123 public static File buildJar(String testDir, String className, String code, String folder) 124 throws Exception { 125 String javaCode = code != null ? code : "public class " + className + " {}"; 126 Path srcDir = new Path(testDir, "src"); 127 File srcDirPath = new File(srcDir.toString()); 128 srcDirPath.mkdirs(); 129 File sourceCodeFile = new File(srcDir.toString(), className + ".java"); 130 BufferedWriter bw = Files.newBufferedWriter(sourceCodeFile.toPath(), StandardCharsets.UTF_8); 131 bw.write(javaCode); 132 bw.close(); 133 134 // compile it by JavaCompiler 135 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 136 StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null); 137 Iterable<? extends JavaFileObject> cu = fm.getJavaFileObjects(sourceCodeFile); 138 List<String> options = new ArrayList<>(2); 139 options.add("-classpath"); 140 // only add hbase classes to classpath. This is a little bit tricky: assume 141 // the classpath is {hbaseSrc}/target/classes. 142 String currentDir = new File(".").getAbsolutePath(); 143 String classpath = currentDir + File.separator + "target" + File.separator + "classes" 144 + System.getProperty("path.separator") + System.getProperty("java.class.path") 145 + System.getProperty("path.separator") + System.getProperty("surefire.test.class.path"); 146 147 options.add(classpath); 148 LOG.debug("Setting classpath to: " + classpath); 149 150 JavaCompiler.CompilationTask task = compiler.getTask(null, fm, null, options, null, cu); 151 assertTrue("Compile file " + sourceCodeFile + " failed.", task.call()); 152 153 // build a jar file by the classes files 154 String jarFileName = className + ".jar"; 155 File jarFile = new File(folder, jarFileName); 156 jarFile.getParentFile().mkdirs(); 157 if ( 158 !createJarArchive(jarFile, new File[] { new File(srcDir.toString(), className + ".class") }) 159 ) { 160 fail("Build jar file failed."); 161 } 162 return jarFile; 163 } 164 165 /** 166 * Add a list of jar files to another jar file under a specific folder. It is used to generated 167 * coprocessor jar files which can be loaded by the coprocessor class loader. It is for testing 168 * usage only so we don't be so careful about stream closing in case any exception. 169 * @param targetJar the target jar file 170 * @param libPrefix the folder where to put inner jar files 171 * @param srcJars the source inner jar files to be added 172 * @throws Exception if anything doesn't work as expected 173 */ 174 public static void addJarFilesToJar(File targetJar, String libPrefix, File... srcJars) 175 throws Exception { 176 FileOutputStream stream = new FileOutputStream(targetJar); 177 JarOutputStream out = new JarOutputStream(stream, new Manifest()); 178 byte[] buffer = new byte[BUFFER_SIZE]; 179 180 for (File jarFile : srcJars) { 181 // Add archive entry 182 JarEntry jarAdd = new JarEntry(libPrefix + jarFile.getName()); 183 jarAdd.setTime(jarFile.lastModified()); 184 out.putNextEntry(jarAdd); 185 186 // Write file to archive 187 FileInputStream in = new FileInputStream(jarFile); 188 while (true) { 189 int nRead = in.read(buffer, 0, buffer.length); 190 if (nRead <= 0) { 191 break; 192 } 193 194 out.write(buffer, 0, nRead); 195 } 196 in.close(); 197 } 198 out.close(); 199 stream.close(); 200 LOG.info("Adding jar file to outer jar file completed"); 201 } 202 203 public static String localDirPath(Configuration conf) { 204 return conf.get(ClassLoaderBase.LOCAL_DIR_KEY) + File.separator + "jars" + File.separator; 205 } 206 207 public static void deleteClass(String className, String testDir, Configuration conf) 208 throws Exception { 209 String jarFileName = className + ".jar"; 210 File file = new File(testDir, jarFileName); 211 file.delete(); 212 assertFalse("Should be deleted: " + file.getPath(), file.exists()); 213 214 file = new File(conf.get("hbase.dynamic.jars.dir"), jarFileName); 215 file.delete(); 216 assertFalse("Should be deleted: " + file.getPath(), file.exists()); 217 218 file = new File(ClassLoaderTestHelper.localDirPath(conf), jarFileName); 219 file.delete(); 220 assertFalse("Should be deleted: " + file.getPath(), file.exists()); 221 } 222}