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