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.util.ArrayList;
28  import java.util.List;
29  import java.util.jar.JarEntry;
30  import java.util.jar.JarOutputStream;
31  import java.util.jar.Manifest;
32  
33  import javax.tools.JavaCompiler;
34  import javax.tools.JavaFileObject;
35  import javax.tools.StandardJavaFileManager;
36  import javax.tools.ToolProvider;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.hadoop.fs.Path;
41  
42  /**
43   * Some utilities to help class loader testing
44   */
45  public class ClassLoaderTestHelper {
46    private static final Log LOG = LogFactory.getLog(ClassLoaderTestHelper.class);
47  
48    /**
49     * Jar a list of files into a jar archive.
50     *
51     * @param archiveFile the target jar archive
52     * @param tobejared a list of files to be jared
53     */
54    private static boolean createJarArchive(File archiveFile, File[] tobeJared) {
55      try {
56        byte buffer[] = new byte[4096];
57        // Open archive file
58        FileOutputStream stream = new FileOutputStream(archiveFile);
59        JarOutputStream out = new JarOutputStream(stream, new Manifest());
60  
61        for (int i = 0; i < tobeJared.length; i++) {
62          if (tobeJared[i] == null || !tobeJared[i].exists()
63              || tobeJared[i].isDirectory()) {
64            continue;
65          }
66  
67          // Add archive entry
68          JarEntry jarAdd = new JarEntry(tobeJared[i].getName());
69          jarAdd.setTime(tobeJared[i].lastModified());
70          out.putNextEntry(jarAdd);
71  
72          // Write file to archive
73          FileInputStream in = new FileInputStream(tobeJared[i]);
74          while (true) {
75            int nRead = in.read(buffer, 0, buffer.length);
76            if (nRead <= 0)
77              break;
78            out.write(buffer, 0, nRead);
79          }
80          in.close();
81        }
82        out.close();
83        stream.close();
84        LOG.info("Adding classes to jar file completed");
85        return true;
86      } catch (Exception ex) {
87        LOG.error("Error: " + ex.getMessage());
88        return false;
89      }
90    }
91  
92    /**
93     * Create a test jar for testing purpose for a given class
94     * name with specified code string: save the class to a file,
95     * compile it, and jar it up. If the code string passed in is
96     * null, a bare empty class will be created and used.
97     *
98     * @param testDir the folder under which to store the test class and jar
99     * @param className the test class name
100    * @param code the optional test class code, which can be null.
101    * If null, a bare empty class will be used
102    * @return the test jar file generated
103    */
104   public static File buildJar(String testDir,
105       String className, String code) throws Exception {
106     return buildJar(testDir, className, code, testDir);
107   }
108 
109   /**
110    * Create a test jar for testing purpose for a given class
111    * name with specified code string.
112    *
113    * @param testDir the folder under which to store the test class
114    * @param className the test class name
115    * @param code the optional test class code, which can be null.
116    * If null, an empty class will be used
117    * @param folder the folder under which to store the generated jar
118    * @return the test jar file generated
119    */
120   public static File buildJar(String testDir,
121       String className, String code, String folder) throws Exception {
122     String javaCode = code != null ? code : "public class " + className + " {}";
123     Path srcDir = new Path(testDir, "src");
124     File srcDirPath = new File(srcDir.toString());
125     srcDirPath.mkdirs();
126     File sourceCodeFile = new File(srcDir.toString(), className + ".java");
127     BufferedWriter bw = new BufferedWriter(new FileWriter(sourceCodeFile));
128     bw.write(javaCode);
129     bw.close();
130 
131     // compile it by JavaCompiler
132     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
133     ArrayList<String> srcFileNames = new ArrayList<String>();
134     srcFileNames.add(sourceCodeFile.toString());
135     StandardJavaFileManager fm = compiler.getStandardFileManager(null, null,
136       null);
137     Iterable<? extends JavaFileObject> cu =
138       fm.getJavaFileObjects(sourceCodeFile);
139     List<String> options = new ArrayList<String>();
140     options.add("-classpath");
141     // only add hbase classes to classpath. This is a little bit tricky: assume
142     // the classpath is {hbaseSrc}/target/classes.
143     String currentDir = new File(".").getAbsolutePath();
144     String classpath = currentDir + File.separator + "target"+ File.separator
145       + "classes" + System.getProperty("path.separator")
146       + System.getProperty("java.class.path") + System.getProperty("path.separator")
147       + System.getProperty("surefire.test.class.path");
148     options.add(classpath);
149     LOG.debug("Setting classpath to: " + classpath);
150 
151     JavaCompiler.CompilationTask task = compiler.getTask(null, fm, null,
152       options, null, cu);
153     assertTrue("Compile file " + sourceCodeFile + " failed.", task.call());
154 
155     // build a jar file by the classes files
156     String jarFileName = className + ".jar";
157     File jarFile = new File(folder, jarFileName);
158     if (!createJarArchive(jarFile,
159         new File[]{new File(srcDir.toString(), className + ".class")})){
160       assertTrue("Build jar file failed.", false);
161     }
162     return jarFile;
163   }
164 }