1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.util;
20  
21  import static org.junit.Assert.assertTrue;
22  import static org.junit.Assert.fail;
23  
24  import java.io.BufferedWriter;
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.FileOutputStream;
28  import java.io.FileWriter;
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  import org.apache.hadoop.hbase.HBaseConfiguration;
45  import org.apache.hadoop.hbase.HBaseTestingUtility;
46  import org.apache.hadoop.hbase.SmallTests;
47  import org.junit.Test;
48  import org.junit.experimental.categories.Category;
49  
50  /**
51   * Test TestDynamicClassLoader
52   */
53  @Category(SmallTests.class)
54  public class TestDynamicClassLoader {
55    private static final Log LOG = LogFactory.getLog(TestDynamicClassLoader.class);
56  
57    private static final Configuration conf = HBaseConfiguration.create();
58  
59    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
60  
61    static {
62      conf.set("hbase.dynamic.jars.dir", TEST_UTIL.getDataTestDir().toString());
63    }
64  
65    // generate jar file
66    private boolean createJarArchive(File archiveFile, File[] tobeJared) {
67      try {
68        byte buffer[] = new byte[4096];
69        // Open archive file
70        FileOutputStream stream = new FileOutputStream(archiveFile);
71        JarOutputStream out = new JarOutputStream(stream, new Manifest());
72  
73        for (int i = 0; i < tobeJared.length; i++) {
74          if (tobeJared[i] == null || !tobeJared[i].exists()
75              || tobeJared[i].isDirectory()) {
76            continue;
77          }
78  
79          // Add archive entry
80          JarEntry jarAdd = new JarEntry(tobeJared[i].getName());
81          jarAdd.setTime(tobeJared[i].lastModified());
82          out.putNextEntry(jarAdd);
83  
84          // Write file to archive
85          FileInputStream in = new FileInputStream(tobeJared[i]);
86          while (true) {
87            int nRead = in.read(buffer, 0, buffer.length);
88            if (nRead <= 0)
89              break;
90            out.write(buffer, 0, nRead);
91          }
92          in.close();
93        }
94        out.close();
95        stream.close();
96        LOG.info("Adding classes to jar file completed");
97        return true;
98      } catch (Exception ex) {
99        LOG.error("Error: " + ex.getMessage());
100       return false;
101     }
102   }
103 
104   private File buildJar(
105       String className, String folder) throws Exception {
106     String javaCode = "public class " + className + " {}";
107     Path srcDir = new Path(TEST_UTIL.getDataTestDir(), "src");
108     File srcDirPath = new File(srcDir.toString());
109     srcDirPath.mkdirs();
110     File sourceCodeFile = new File(srcDir.toString(), className + ".java");
111     BufferedWriter bw = new BufferedWriter(new FileWriter(sourceCodeFile));
112     bw.write(javaCode);
113     bw.close();
114 
115     // compile it by JavaCompiler
116     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
117     ArrayList<String> srcFileNames = new ArrayList<String>();
118     srcFileNames.add(sourceCodeFile.toString());
119     StandardJavaFileManager fm = compiler.getStandardFileManager(null, null,
120       null);
121     Iterable<? extends JavaFileObject> cu =
122       fm.getJavaFileObjects(sourceCodeFile);
123     List<String> options = new ArrayList<String>();
124     options.add("-classpath");
125     // only add hbase classes to classpath. This is a little bit tricky: assume
126     // the classpath is {hbaseSrc}/target/classes.
127     String currentDir = new File(".").getAbsolutePath();
128     String classpath =
129         currentDir + File.separator + "target"+ File.separator + "classes" +
130         System.getProperty("path.separator") + System.getProperty("java.class.path");
131     options.add(classpath);
132     LOG.debug("Setting classpath to: "+classpath);
133 
134     JavaCompiler.CompilationTask task = compiler.getTask(null, fm, null,
135       options, null, cu);
136     assertTrue("Compile file " + sourceCodeFile + " failed.", task.call());
137 
138     // build a jar file by the classes files
139     String jarFileName = className + ".jar";
140     File jarFile = new File(folder, jarFileName);
141     if (!createJarArchive(jarFile,
142         new File[]{new File(srcDir.toString(), className + ".class")})){
143       assertTrue("Build jar file failed.", false);
144     }
145     return jarFile;
146   }
147 
148   @Test
149   public void testLoadClassFromLocalPath() throws Exception {
150     ClassLoader parent = TestDynamicClassLoader.class.getClassLoader();
151     DynamicClassLoader classLoader = new DynamicClassLoader(conf, parent);
152 
153     String className = "TestLoadClassFromLocalPath";
154     try {
155       classLoader.loadClass(className);
156       fail("Should not be able to load class " + className);
157     } catch (ClassNotFoundException cnfe) {
158       // expected, move on
159     }
160 
161     try {
162       buildJar(className, localDirPath());
163       classLoader.loadClass(className);
164     } catch (ClassNotFoundException cnfe) {
165       LOG.error("Should be able to load class " + className, cnfe);
166       fail(cnfe.getMessage());
167     } finally {
168       deleteClass(className);
169     }
170   }
171 
172   @Test
173   public void testLoadClassFromAnotherPath() throws Exception {
174     ClassLoader parent = TestDynamicClassLoader.class.getClassLoader();
175     DynamicClassLoader classLoader = new DynamicClassLoader(conf, parent);
176 
177     String className = "TestLoadClassFromAnotherPath";
178     try {
179       classLoader.loadClass(className);
180       fail("Should not be able to load class " + className);
181     } catch (ClassNotFoundException cnfe) {
182       // expected, move on
183     }
184 
185     try {
186       buildJar(className, TEST_UTIL.getDataTestDir().toString());
187       classLoader.loadClass(className);
188     } catch (ClassNotFoundException cnfe) {
189       LOG.error("Should be able to load class " + className, cnfe);
190       fail(cnfe.getMessage());
191     } finally {
192       deleteClass(className);
193     }
194   }
195 
196   private String localDirPath() {
197     return conf.get("hbase.local.dir") + File.separator
198       + "dynamic" + File.separator + "jars" + File.separator;
199   }
200 
201   private void deleteClass(String className) throws Exception {
202     String jarFileName = className + ".jar";
203     File file = new File(TEST_UTIL.getDataTestDir().toString(), jarFileName);
204     file.deleteOnExit();
205 
206     file = new File(conf.get("hbase.dynamic.jars.dir"), jarFileName);
207     file.deleteOnExit();
208 
209     file = new File(localDirPath(), jarFileName);
210     file.deleteOnExit();
211   }
212 
213   @org.junit.Rule
214   public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
215     new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
216 }