View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.util;
19  
20  import static org.junit.Assert.assertTrue;
21  import static org.junit.Assert.fail;
22  
23  import java.io.BufferedWriter;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileOutputStream;
27  import java.io.FileWriter;
28  import java.nio.charset.StandardCharsets;
29  import java.nio.file.Files;
30  import java.util.ArrayList;
31  import java.util.List;
32  import java.util.jar.JarEntry;
33  import java.util.jar.JarOutputStream;
34  import java.util.jar.Manifest;
35  
36  import javax.tools.JavaCompiler;
37  import javax.tools.JavaFileObject;
38  import javax.tools.StandardJavaFileManager;
39  import javax.tools.ToolProvider;
40  
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.apache.hadoop.conf.Configuration;
44  import org.apache.hadoop.fs.Path;
45  
46  /**
47   * Some utilities to help class loader testing
48   */
49  public final class ClassLoaderTestHelper {
50    private static final Log LOG = LogFactory.getLog(ClassLoaderTestHelper.class);
51  
52    private static final int BUFFER_SIZE = 4096;
53  
54    private ClassLoaderTestHelper() {
55    }
56  
57    /**
58     * Jar a list of files into a jar archive.
59     *
60     * @param archiveFile the target jar archive
61     * @param tobeJared a list of files to be jared
62     * @return true if a jar archive is build, false otherwise
63     */
64    private static boolean createJarArchive(File archiveFile, File[] tobeJared) {
65      try {
66        byte[] buffer = new byte[BUFFER_SIZE];
67        // Open archive file
68        FileOutputStream stream = new FileOutputStream(archiveFile);
69        JarOutputStream out = new JarOutputStream(stream, new Manifest());
70  
71        for (File file : tobeJared) {
72          if (file == null || !file.exists() || file.isDirectory()) {
73            continue;
74          }
75  
76          // Add archive entry
77          JarEntry jarAdd = new JarEntry(file.getName());
78          jarAdd.setTime(file.lastModified());
79          out.putNextEntry(jarAdd);
80  
81          // Write file to archive
82          FileInputStream in = new FileInputStream(file);
83          while (true) {
84            int nRead = in.read(buffer, 0, buffer.length);
85            if (nRead <= 0) {
86              break;
87            }
88  
89            out.write(buffer, 0, nRead);
90          }
91          in.close();
92        }
93        out.close();
94        stream.close();
95        LOG.info("Adding classes to jar file completed");
96        return true;
97      } catch (Exception ex) {
98        LOG.error("Error: " + ex.getMessage());
99        return false;
100     }
101   }
102 
103   /**
104    * Create a test jar for testing purpose for a given class
105    * name with specified code string: save the class to a file,
106    * compile it, and jar it up. If the code string passed in is
107    * null, a bare empty class will be created and used.
108    *
109    * @param testDir the folder under which to store the test class and jar
110    * @param className the test class name
111    * @param code the optional test class code, which can be null.
112    *    If null, a bare empty class will be used
113    * @return the test jar file generated
114    */
115   public static File buildJar(String testDir,
116       String className, String code) throws Exception {
117     return buildJar(testDir, className, code, testDir);
118   }
119 
120   /**
121    * Create a test jar for testing purpose for a given class
122    * name with specified code string.
123    *
124    * @param testDir the folder under which to store the test class
125    * @param className the test class name
126    * @param code the optional test class code, which can be null.
127    *    If null, an empty class will be used
128    * @param folder the folder under which to store the generated jar
129    * @return the test jar file generated
130    */
131   public static File buildJar(String testDir,
132       String className, String code, String folder) throws Exception {
133     String javaCode = code != null ? code : "public class " + className + " {}";
134     Path srcDir = new Path(testDir, "src");
135     File srcDirPath = new File(srcDir.toString());
136     srcDirPath.mkdirs();
137     File sourceCodeFile = new File(srcDir.toString(), className + ".java");
138     BufferedWriter bw = Files.newBufferedWriter(sourceCodeFile.toPath(), StandardCharsets.UTF_8);
139     bw.write(javaCode);
140     bw.close();
141 
142     // compile it by JavaCompiler
143     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
144     ArrayList<String> srcFileNames = new ArrayList<>();
145     srcFileNames.add(sourceCodeFile.toString());
146     StandardJavaFileManager fm = compiler.getStandardFileManager(null, null,
147       null);
148     Iterable<? extends JavaFileObject> cu =
149       fm.getJavaFileObjects(sourceCodeFile);
150     List<String> options = new ArrayList<>();
151     options.add("-classpath");
152     // only add hbase classes to classpath. This is a little bit tricky: assume
153     // the classpath is {hbaseSrc}/target/classes.
154     String currentDir = new File(".").getAbsolutePath();
155     String classpath = currentDir + File.separator + "target"+ File.separator
156       + "classes" + System.getProperty("path.separator")
157       + System.getProperty("java.class.path") + System.getProperty("path.separator")
158       + System.getProperty("surefire.test.class.path");
159 
160     options.add(classpath);
161     LOG.debug("Setting classpath to: " + classpath);
162 
163     JavaCompiler.CompilationTask task = compiler.getTask(null, fm, null,
164       options, null, cu);
165     assertTrue("Compile file " + sourceCodeFile + " failed.", task.call());
166 
167     // build a jar file by the classes files
168     String jarFileName = className + ".jar";
169     File jarFile = new File(folder, jarFileName);
170     jarFile.getParentFile().mkdirs();
171     if (!createJarArchive(jarFile,
172         new File[]{new File(srcDir.toString(), className + ".class")})){
173       fail("Build jar file failed.");
174     }
175     return jarFile;
176   }
177 
178   /**
179    * Add a list of jar files to another jar file under a specific folder.
180    * It is used to generated coprocessor jar files which can be loaded by
181    * the coprocessor class loader.  It is for testing usage only so we
182    * don't be so careful about stream closing in case any exception.
183    *
184    * @param targetJar the target jar file
185    * @param libPrefix the folder where to put inner jar files
186    * @param srcJars the source inner jar files to be added
187    * @throws Exception if anything doesn't work as expected
188    */
189   public static void addJarFilesToJar(File targetJar,
190       String libPrefix, File... srcJars) throws Exception {
191     FileOutputStream stream = new FileOutputStream(targetJar);
192     JarOutputStream out = new JarOutputStream(stream, new Manifest());
193     byte[] buffer = new byte[BUFFER_SIZE];
194 
195     for (File jarFile: srcJars) {
196       // Add archive entry
197       JarEntry jarAdd = new JarEntry(libPrefix + jarFile.getName());
198       jarAdd.setTime(jarFile.lastModified());
199       out.putNextEntry(jarAdd);
200 
201       // Write file to archive
202       FileInputStream in = new FileInputStream(jarFile);
203       while (true) {
204         int nRead = in.read(buffer, 0, buffer.length);
205         if (nRead <= 0) {
206           break;
207         }
208 
209         out.write(buffer, 0, nRead);
210       }
211       in.close();
212     }
213     out.close();
214     stream.close();
215     LOG.info("Adding jar file to outer jar file completed");
216   }
217 
218   static String localDirPath(Configuration conf) {
219     return conf.get(ClassLoaderBase.LOCAL_DIR_KEY)
220       + File.separator + "jars" + File.separator;
221   }
222 }