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,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.hadoop.hbase.mapreduce;
020
021import java.io.BufferedOutputStream;
022import java.io.File;
023import java.io.FileInputStream;
024import java.io.FileOutputStream;
025import java.io.IOException;
026import java.io.InputStream;
027import java.net.URL;
028import java.net.URLDecoder;
029import java.text.MessageFormat;
030import java.util.Enumeration;
031import java.util.jar.JarFile;
032import java.util.jar.JarOutputStream;
033import java.util.jar.Manifest;
034import java.util.zip.ZipEntry;
035import java.util.zip.ZipOutputStream;
036import org.apache.yetus.audience.InterfaceAudience;
037
038import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
039
040/**
041 * Finds the Jar for a class. If the class is in a directory in the
042 * classpath, it creates a Jar on the fly with the contents of the directory
043 * and returns the path to that Jar. If a Jar is created, it is created in
044 * the system temporary directory.
045 *
046 * This file was forked from hadoop/common/branches/branch-2@1377176.
047 */
048@InterfaceAudience.Private
049public final class JarFinder {
050
051  private static void copyToZipStream(File file, ZipEntry entry,
052                              ZipOutputStream zos) throws IOException {
053    InputStream is = new FileInputStream(file);
054    try {
055      zos.putNextEntry(entry);
056      byte[] arr = new byte[4096];
057      int read = is.read(arr);
058      while (read > -1) {
059        zos.write(arr, 0, read);
060        read = is.read(arr);
061      }
062    } finally {
063      try {
064        is.close();
065      } finally {
066        zos.closeEntry();
067      }
068    }
069  }
070
071  public static void jarDir(File dir, String relativePath, ZipOutputStream zos)
072    throws IOException {
073    Preconditions.checkNotNull(relativePath, "relativePath");
074    Preconditions.checkNotNull(zos, "zos");
075
076    // by JAR spec, if there is a manifest, it must be the first entry in the
077    // ZIP.
078    File manifestFile = new File(dir, JarFile.MANIFEST_NAME);
079    ZipEntry manifestEntry = new ZipEntry(JarFile.MANIFEST_NAME);
080    if (!manifestFile.exists()) {
081      zos.putNextEntry(manifestEntry);
082      new Manifest().write(new BufferedOutputStream(zos));
083      zos.closeEntry();
084    } else {
085      copyToZipStream(manifestFile, manifestEntry, zos);
086    }
087    zos.closeEntry();
088    zipDir(dir, relativePath, zos, true);
089    zos.close();
090  }
091
092  private static void zipDir(File dir, String relativePath, ZipOutputStream zos,
093                             boolean start) throws IOException {
094    String[] dirList = dir.list();
095    if (dirList == null) {
096      return;
097    }
098    for (String aDirList : dirList) {
099      File f = new File(dir, aDirList);
100      if (!f.isHidden()) {
101        if (f.isDirectory()) {
102          if (!start) {
103            ZipEntry dirEntry = new ZipEntry(relativePath + f.getName() + "/");
104            zos.putNextEntry(dirEntry);
105            zos.closeEntry();
106          }
107          String filePath = f.getPath();
108          File file = new File(filePath);
109          zipDir(file, relativePath + f.getName() + "/", zos, false);
110        }
111        else {
112          String path = relativePath + f.getName();
113          if (!path.equals(JarFile.MANIFEST_NAME)) {
114            ZipEntry anEntry = new ZipEntry(path);
115            copyToZipStream(f, anEntry, zos);
116          }
117        }
118      }
119    }
120  }
121
122  private static void createJar(File dir, File jarFile) throws IOException {
123    Preconditions.checkNotNull(dir, "dir");
124    Preconditions.checkNotNull(jarFile, "jarFile");
125    File jarDir = jarFile.getParentFile();
126    if (!jarDir.exists()) {
127      if (!jarDir.mkdirs()) {
128        throw new IOException(MessageFormat.format("could not create dir [{0}]",
129                                                   jarDir));
130      }
131    }
132    try (FileOutputStream fos = new FileOutputStream(jarFile);
133         JarOutputStream jos = new JarOutputStream(fos)) {
134      jarDir(dir, "", jos);
135    }
136  }
137
138  /**
139   * Returns the full path to the Jar containing the class. It always return a
140   * JAR.
141   *
142   * @param klass class.
143   *
144   * @return path to the Jar containing the class.
145   */
146  public static String getJar(Class klass) {
147    Preconditions.checkNotNull(klass, "klass");
148    ClassLoader loader = klass.getClassLoader();
149    if (loader != null) {
150      String class_file = klass.getName().replaceAll("\\.", "/") + ".class";
151      try {
152        for (Enumeration itr = loader.getResources(class_file);
153             itr.hasMoreElements(); ) {
154          URL url = (URL) itr.nextElement();
155          String path = url.getPath();
156          if (path.startsWith("file:")) {
157            path = path.substring("file:".length());
158          }
159          path = URLDecoder.decode(path, "UTF-8");
160          if ("jar".equals(url.getProtocol())) {
161            path = URLDecoder.decode(path, "UTF-8");
162            return path.replaceAll("!.*$", "");
163          }
164          else if ("file".equals(url.getProtocol())) {
165            String klassName = klass.getName();
166            klassName = klassName.replace(".", "/") + ".class";
167            path = path.substring(0, path.length() - klassName.length());
168            File baseDir = new File(path);
169            File testDir = new File(System.getProperty("test.build.dir", "target/test-dir"));
170            testDir = testDir.getAbsoluteFile();
171            if (!testDir.exists()) {
172              testDir.mkdirs();
173            }
174            File tempJar = File.createTempFile("hadoop-", "", testDir);
175            tempJar = new File(tempJar.getAbsolutePath() + ".jar");
176            tempJar.deleteOnExit();
177            createJar(baseDir, tempJar);
178            return tempJar.getAbsolutePath();
179          }
180        }
181      }
182      catch (IOException e) {
183        throw new RuntimeException(e);
184      }
185    }
186    return null;
187  }
188
189  private JarFinder() {}
190}