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