/* * Copyright 2013 Cloudera Inc. * * Licensed 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.kitesdk.cli.commands; import com.beust.jcommander.internal.Lists; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.io.Resources; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.Charset; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.List; import org.apache.hadoop.conf.Configurable; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.ChecksumFileSystem; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.LocalFileSystem; import org.apache.hadoop.fs.Path; import org.kitesdk.cli.Command; import org.kitesdk.data.spi.HadoopFileSystemURLStreamHandler; import org.slf4j.Logger; public abstract class BaseCommand implements Command, Configurable { @VisibleForTesting static final Charset UTF8 = Charset.forName("utf8"); private static final String RESOURCE_URI_SCHEME = "resource"; private static final String STDIN_AS_SOURCE = "stdin"; private Configuration conf = null; private LocalFileSystem localFS = null; /** * @return FileSystem to use when no file system scheme is present in a path * @throws IOException */ public FileSystem defaultFS() throws IOException { if (localFS == null) { this.localFS = FileSystem.getLocal(getConf()); } return localFS; } /** * Output content to the console or a file. * * This will not produce checksum files. * * @param content String content to write * @param console A {@link Logger} for writing to the console * @param filename The destination {@link Path} as a String * @throws IOException */ public void output(String content, Logger console, String filename) throws IOException { if (filename == null || "-".equals(filename)) { console.info(content); } else { FSDataOutputStream outgoing = create(filename); try { outgoing.write(content.getBytes(UTF8)); } finally { outgoing.close(); } } } /** * Creates a file and returns an open {@link FSDataOutputStream}. * * If the file does not have a file system scheme, this uses the default FS. * * This will not produce checksum files and will overwrite a file that * already exists. * * @param filename The filename to create * @return An open FSDataOutputStream * @throws IOException */ public FSDataOutputStream create(String filename) throws IOException { return create(filename, true); } /** * Creates a file and returns an open {@link FSDataOutputStream}. * * If the file does not have a file system scheme, this uses the default FS. * * This will produce checksum files and will overwrite a file that already * exists. * * @param filename The filename to create * @return An open FSDataOutputStream * @throws IOException */ public FSDataOutputStream createWithChecksum(String filename) throws IOException { return create(filename, false); } private FSDataOutputStream create(String filename, boolean noChecksum) throws IOException { Path filePath = qualifiedPath(filename); // even though it was qualified using the default FS, it may not be in it FileSystem fs = filePath.getFileSystem(getConf()); if (noChecksum && fs instanceof ChecksumFileSystem) { fs = ((ChecksumFileSystem) fs).getRawFileSystem(); } return fs.create(filePath, true /* overwrite */); } /** * Returns a qualified {@link Path} for the {@code filename}. * * If the file does not have a file system scheme, this uses the default FS. * * @param filename The filename to qualify * @return A qualified Path for the filename * @throws IOException */ public Path qualifiedPath(String filename) throws IOException { Path cwd = defaultFS().makeQualified(new Path(".")); return new Path(filename).makeQualified(defaultFS().getUri(), cwd); } /** * Returns a {@link URI} for the {@code filename} that is a qualified Path or * a resource URI. * * If the file does not have a file system scheme, this uses the default FS. * * @param filename The filename to qualify * @return A qualified URI for the filename * @throws IOException */ public URI qualifiedURI(String filename) throws IOException { URI fileURI = URI.create(filename); if (RESOURCE_URI_SCHEME.equals(fileURI.getScheme())) { return fileURI; } else { return qualifiedPath(filename).toUri(); } } /** * Opens an existing file or resource. * * If the file does not have a file system scheme, this uses the default FS. * * @param filename The filename to open. * @return An open InputStream with the file contents * @throws IOException * @throws IllegalArgumentException If the file does not exist */ public InputStream open(String filename) throws IOException { if (STDIN_AS_SOURCE.equals(filename)) { return System.in; } URI uri = qualifiedURI(filename); if (RESOURCE_URI_SCHEME.equals(uri.getScheme())) { return Resources.getResource(uri.getRawSchemeSpecificPart()).openStream(); } else { Path filePath = new Path(uri); // even though it was qualified using the default FS, it may not be in it FileSystem fs = filePath.getFileSystem(getConf()); return fs.open(filePath); } } @Override public void setConf(Configuration conf) { this.conf = conf; HadoopFileSystemURLStreamHandler.setDefaultConf(conf); } @Override public Configuration getConf() { return conf; } /** * Returns a {@link ClassLoader} for a set of jars and directories. * * @param jars A list of jar paths * @param paths A list of directories containing .class files * @throws MalformedURLException */ protected static ClassLoader loaderFor(List<String> jars, List<String> paths) throws MalformedURLException { return AccessController.doPrivileged(new GetClassLoader(urls(jars, paths))); } /** * Returns a {@link ClassLoader} for a set of jars. * * @param jars A list of jar paths * @throws MalformedURLException */ protected static ClassLoader loaderForJars(List<String> jars) throws MalformedURLException { return AccessController.doPrivileged(new GetClassLoader(urls(jars, null))); } /** * Returns a {@link ClassLoader} for a set of directories. * * @param paths A list of directories containing .class files * @throws MalformedURLException */ protected static ClassLoader loaderForPaths(List<String> paths) throws MalformedURLException { return AccessController.doPrivileged(new GetClassLoader(urls(null, paths))); } private static List<URL> urls(List<String> jars, List<String> dirs) throws MalformedURLException { // check the additional jars and lib directories in the local FS final List<URL> urls = Lists.newArrayList(); if (dirs != null) { for (String lib : dirs) { // final URLs must end in '/' for URLClassLoader File path = lib.endsWith("/") ? new File(lib) : new File(lib + "/"); Preconditions.checkArgument(path.exists(), "Lib directory does not exist: " + lib); Preconditions.checkArgument(path.isDirectory(), "Not a directory: " + lib); Preconditions.checkArgument(path.canRead() && path.canExecute(), "Insufficient permissions to access lib directory: " + lib); urls.add(path.toURI().toURL()); } } if (jars != null) { for (String jar : jars) { File path = new File(jar); Preconditions.checkArgument(path.exists(), "Jar files does not exist: " + jar); Preconditions.checkArgument(path.isFile(), "Not a file: " + jar); Preconditions.checkArgument(path.canRead(), "Cannot read jar file: " + jar); urls.add(path.toURI().toURL()); } } return urls; } private static class GetClassLoader implements PrivilegedAction<ClassLoader> { private final URL[] urls; public GetClassLoader(List<URL> urls) { this.urls = urls.toArray(new URL[urls.size()]); } @Override public ClassLoader run() { return new URLClassLoader( urls, Thread.currentThread().getContextClassLoader()); } } }