| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.ignite.internal.processors.hadoop; |
| |
| import java.io.File; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.LinkedList; |
| import java.util.List; |
| import org.apache.ignite.internal.util.typedef.F; |
| |
| /** |
| * Hadoop classpath utilities. |
| */ |
| public class HadoopClasspathUtils { |
| /** Prefix directory. */ |
| public static final String PREFIX = "HADOOP_PREFIX"; |
| |
| /** Hadoop home directory. */ |
| public static final String HOME = "HADOOP_HOME"; |
| |
| /** Hadoop common directory. */ |
| public static final String COMMON_HOME = "HADOOP_COMMON_HOME"; |
| |
| /** Hadoop HDFS directory. */ |
| public static final String HDFS_HOME = "HADOOP_HDFS_HOME"; |
| |
| /** Hadoop mapred directory. */ |
| public static final String MAPRED_HOME = "HADOOP_MAPRED_HOME"; |
| |
| /** Arbitrary additional dependencies. Compliant with standard Java classpath resolution. */ |
| public static final String HADOOP_USER_LIBS = "HADOOP_USER_LIBS"; |
| |
| /** Empty string. */ |
| private static final String EMPTY_STR = ""; |
| |
| /** |
| * Gets Hadoop class path as a list of URLs (for in-process class loader usage). |
| * |
| * @return List of class path URLs. |
| * @throws IOException If failed. |
| */ |
| public static List<URL> classpathForClassLoader() throws IOException { |
| List<URL> res = new ArrayList<>(); |
| |
| for (SearchDirectory dir : classpathDirectories()) { |
| for (File file : dir.files()) { |
| try { |
| res.add(file.toURI().toURL()); |
| } |
| catch (MalformedURLException ignored) { |
| throw new IOException("Failed to convert file path to URL: " + file.getPath()); |
| } |
| } |
| } |
| |
| return res; |
| } |
| |
| /** |
| * Gets Hadoop locations. |
| * |
| * @return The locations as determined from the environment. |
| */ |
| public static HadoopLocations locations() throws IOException { |
| // Query environment. |
| String hadoopHome = systemOrEnv(PREFIX, systemOrEnv(HOME, EMPTY_STR)); |
| |
| String commonHome = systemOrEnv(COMMON_HOME, EMPTY_STR); |
| String hdfsHome = systemOrEnv(HDFS_HOME, EMPTY_STR); |
| String mapredHome = systemOrEnv(MAPRED_HOME, EMPTY_STR); |
| |
| // If any composite location is defined, use only them. |
| if (!isEmpty(commonHome) || !isEmpty(hdfsHome) || !isEmpty(mapredHome)) { |
| HadoopLocations res = new HadoopLocations(hadoopHome, commonHome, hdfsHome, mapredHome); |
| |
| if (res.valid()) |
| return res; |
| else |
| throw new IOException("Failed to resolve Hadoop classpath because some environment variables are " + |
| "either undefined or point to nonexistent directories [" + |
| "[env=" + COMMON_HOME + ", value=" + commonHome + ", exists=" + res.commonExists() + "], " + |
| "[env=" + HDFS_HOME + ", value=" + hdfsHome + ", exists=" + res.hdfsExists() + "], " + |
| "[env=" + MAPRED_HOME + ", value=" + mapredHome + ", exists=" + res.mapredExists() + "]]"); |
| } |
| else if (!isEmpty(hadoopHome)) { |
| // All further checks will be based on HADOOP_HOME, so check for it's existence. |
| if (!exists(hadoopHome)) |
| throw new IOException("Failed to resolve Hadoop classpath because " + HOME + " environment " + |
| "variable points to nonexistent directory: " + hadoopHome); |
| |
| // Probe Apache Hadoop. |
| HadoopLocations res = new HadoopLocations( |
| hadoopHome, |
| hadoopHome + "/share/hadoop/common", |
| hadoopHome + "/share/hadoop/hdfs", |
| hadoopHome + "/share/hadoop/mapreduce" |
| ); |
| |
| if (res.valid()) |
| return res; |
| |
| // Probe CDH. |
| res = new HadoopLocations( |
| hadoopHome, |
| hadoopHome, |
| hadoopHome + "/../hadoop-hdfs", |
| hadoopHome + "/../hadoop-mapreduce" |
| ); |
| |
| if (res.valid()) |
| return res; |
| |
| // Probe HDP. |
| res = new HadoopLocations( |
| hadoopHome, |
| hadoopHome, |
| hadoopHome + "/../hadoop-hdfs-client", |
| hadoopHome + "/../hadoop-mapreduce-client" |
| ); |
| |
| if (res.valid()) |
| return res; |
| |
| // Failed. |
| throw new IOException("Failed to resolve Hadoop classpath because " + HOME + " environment variable " + |
| "is either invalid or points to non-standard Hadoop distribution: " + hadoopHome); |
| } |
| else { |
| // Advise to set HADOOP_HOME only as this is preferred way to configure classpath. |
| throw new IOException("Failed to resolve Hadoop classpath (please define " + HOME + " environment " + |
| "variable and point it to your Hadoop distribution)."); |
| } |
| } |
| |
| /** |
| * Gets base directories to discover classpath elements in. |
| * |
| * @return Collection of directory and mask pairs. |
| * @throws IOException if a mandatory classpath location is not found. |
| */ |
| private static Collection<SearchDirectory> classpathDirectories() throws IOException { |
| HadoopLocations loc = locations(); |
| |
| Collection<SearchDirectory> res = new ArrayList<>(); |
| |
| // Add libraries from Hadoop distribution: |
| res.add(new SearchDirectory(new File(loc.common(), "lib"), AcceptAllDirectoryFilter.INSTANCE)); |
| res.add(new SearchDirectory(new File(loc.hdfs(), "lib"), AcceptAllDirectoryFilter.INSTANCE)); |
| res.add(new SearchDirectory(new File(loc.mapred(), "lib"), AcceptAllDirectoryFilter.INSTANCE)); |
| |
| res.add(new SearchDirectory(new File(loc.common()), new PrefixDirectoryFilter("hadoop-common-"))); |
| res.add(new SearchDirectory(new File(loc.common()), new PrefixDirectoryFilter("hadoop-auth-"))); |
| |
| res.add(new SearchDirectory(new File(loc.hdfs()), new PrefixDirectoryFilter("hadoop-hdfs-"))); |
| |
| res.add(new SearchDirectory(new File(loc.mapred()), |
| new PrefixDirectoryFilter("hadoop-mapreduce-client-common"))); |
| res.add(new SearchDirectory(new File(loc.mapred()), |
| new PrefixDirectoryFilter("hadoop-mapreduce-client-core"))); |
| |
| // Add user provided libs: |
| res.addAll(parseUserLibs()); |
| |
| return res; |
| } |
| |
| /** |
| * Parse user libs. |
| * |
| * @return Parsed libs search patterns. |
| * @throws IOException If failed. |
| */ |
| public static Collection<SearchDirectory> parseUserLibs() throws IOException { |
| return parseUserLibs(systemOrEnv(HADOOP_USER_LIBS, null)); |
| } |
| |
| /** |
| * Parse user libs. |
| * |
| * @param str String. |
| * @return Result. |
| * @throws IOException If failed. |
| */ |
| public static Collection<SearchDirectory> parseUserLibs(String str) throws IOException { |
| Collection<SearchDirectory> res = new LinkedList<>(); |
| |
| if (!isEmpty(str)) { |
| String[] tokens = normalize(str).split(File.pathSeparator); |
| |
| for (String token : tokens) { |
| // Skip empty tokens. |
| if (isEmpty(token)) |
| continue; |
| |
| File file = new File(token); |
| File dir = file.getParentFile(); |
| |
| if (token.endsWith("*")) { |
| assert dir != null; |
| |
| res.add(new SearchDirectory(dir, AcceptAllDirectoryFilter.INSTANCE, false)); |
| } |
| else { |
| // Met "/" or "C:\" pattern, nothing to do with it. |
| if (dir == null) |
| continue; |
| |
| res.add(new SearchDirectory(dir, new ExactDirectoryFilter(file.getName()), false)); |
| } |
| } |
| } |
| |
| return res; |
| } |
| |
| /** |
| * Get system property or environment variable with the given name. |
| * |
| * @param name Variable name. |
| * @param dflt Default value. |
| * @return Value. |
| */ |
| private static String systemOrEnv(String name, String dflt) { |
| String res = System.getProperty(name); |
| |
| if (res == null) |
| res = System.getenv(name); |
| |
| return res != null ? res : dflt; |
| } |
| |
| /** |
| * Answers if the given path denotes existing directory. |
| * |
| * @param path The directory path. |
| * @return {@code True} if the given path denotes an existing directory. |
| */ |
| public static boolean exists(String path) { |
| if (F.isEmpty(path)) |
| return false; |
| |
| Path p = Paths.get(path); |
| |
| return Files.exists(p) && Files.isDirectory(p) && Files.isReadable(p); |
| } |
| |
| /** |
| * Check if string is empty. |
| * |
| * @param val Value. |
| * @return {@code True} if empty. |
| */ |
| private static boolean isEmpty(String val) { |
| return val == null || val.isEmpty(); |
| } |
| |
| /** |
| * NOramlize the string. |
| * |
| * @param str String. |
| * @return Normalized string. |
| */ |
| private static String normalize(String str) { |
| assert str != null; |
| |
| return str.trim().toLowerCase(); |
| } |
| |
| /** |
| * Simple pair-like structure to hold directory name and a mask assigned to it. |
| */ |
| public static class SearchDirectory { |
| /** File. */ |
| private final File dir; |
| |
| /** Filter. */ |
| private final DirectoryFilter filter; |
| |
| /** Whether directory must exist. */ |
| private final boolean strict; |
| |
| /** |
| * Constructor for directory search with strict rule. |
| * |
| * @param dir Directory. |
| * @param filter Filter. |
| * @throws IOException If failed. |
| */ |
| private SearchDirectory(File dir, DirectoryFilter filter) throws IOException { |
| this(dir, filter, true); |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param dir Directory. |
| * @param filter Filter. |
| * @param strict Whether directory must exist. |
| * @throws IOException If failed. |
| */ |
| private SearchDirectory(File dir, DirectoryFilter filter, boolean strict) throws IOException { |
| this.dir = dir; |
| this.filter = filter; |
| this.strict = strict; |
| |
| if (strict && !exists(dir.getAbsolutePath())) |
| throw new IOException("Directory cannot be read: " + dir.getAbsolutePath()); |
| } |
| |
| /** |
| * @return Child files. |
| */ |
| public File[] files() throws IOException { |
| File[] files = dir.listFiles(new FilenameFilter() { |
| @Override public boolean accept(File dir, String name) { |
| return filter.test(name); |
| } |
| }); |
| |
| if (files == null) { |
| if (strict) |
| throw new IOException("Failed to get directory files [dir=" + dir + ']'); |
| else |
| return new File[0]; |
| } |
| else |
| return files; |
| } |
| } |
| |
| /** |
| * Directory filter interface. |
| */ |
| public static interface DirectoryFilter { |
| /** |
| * Test if file with this name should be included. |
| * |
| * @param name File name. |
| * @return {@code True} if passed. |
| */ |
| public boolean test(String name); |
| } |
| |
| /** |
| * Filter to accept all files. |
| */ |
| public static class AcceptAllDirectoryFilter implements DirectoryFilter { |
| /** Singleton instance. */ |
| public static final AcceptAllDirectoryFilter INSTANCE = new AcceptAllDirectoryFilter(); |
| |
| /** {@inheritDoc} */ |
| @Override public boolean test(String name) { |
| return true; |
| } |
| } |
| |
| /** |
| * Filter which uses prefix to filter files. |
| */ |
| public static class PrefixDirectoryFilter implements DirectoryFilter { |
| /** Prefix. */ |
| private final String prefix; |
| |
| /** |
| * Constructor. |
| * |
| * @param prefix Prefix. |
| */ |
| public PrefixDirectoryFilter(String prefix) { |
| assert prefix != null; |
| |
| this.prefix = normalize(prefix); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public boolean test(String name) { |
| return normalize(name).startsWith(prefix); |
| } |
| } |
| |
| /** |
| * Filter which uses exact comparison. |
| */ |
| public static class ExactDirectoryFilter implements DirectoryFilter { |
| /** Name. */ |
| private final String name; |
| |
| /** |
| * Constructor. |
| * |
| * @param name Name. |
| */ |
| public ExactDirectoryFilter(String name) { |
| this.name = normalize(name); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public boolean test(String name) { |
| return normalize(name).equals(this.name); |
| } |
| } |
| } |