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