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