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