package io.airlift.airship.shared; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.ByteSource; import com.google.common.io.Files; import com.google.common.io.Resources; import io.airlift.configuration.ConfigurationFactory; import java.io.BufferedInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map.Entry; import java.util.Properties; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import static com.google.common.base.Objects.firstNonNull; import static com.google.common.collect.Maps.newLinkedHashMap; import static java.lang.String.format; public class ConfigUtils { private static final String CONFIG_PROPERTIES = "etc/config.properties"; public static void packConfig(File outputFile, String rootPath, File... inputDirs) throws IOException { FileOutputStream outputStream = new FileOutputStream(outputFile); try { packConfig(outputStream, rootPath, inputDirs); } catch (IOException e) { outputStream.close(); } } public static void packConfig(OutputStream outputStream, String rootPath, File... inputDirs) throws IOException { ZipOutputStream out = new ZipOutputStream(outputStream); try { zipDirectory(out, rootPath, inputDirs); } finally { out.finish(); out.flush(); } } private static void zipDirectory(ZipOutputStream out, String rootPath, File... inputDirs) throws IOException { LinkedHashMap<String, File> files = newLinkedHashMap(); for (File inputDir : Lists.reverse(Arrays.asList(inputDirs))) { listFilesRecursive(rootPath, files, inputDir); } for (Entry<String, File> entry : files.entrySet()) { String path = entry.getKey(); File file = entry.getValue(); if (file.isDirectory()) { if (!path.endsWith("/")) { path = path + "/"; } ZipEntry dirEntry = new ZipEntry(path); dirEntry.setTime(file.lastModified()); out.putNextEntry(dirEntry); } else { ZipEntry fileEntry = new ZipEntry(path); fileEntry.setTime(file.lastModified()); out.putNextEntry(fileEntry); Files.copy(file, out); } } } private static void listFilesRecursive(String path, LinkedHashMap<String, File> files, File dir) { if (!path.isEmpty() && !path.endsWith("/")) { path = path + "/"; } for (File file : firstNonNull(dir.listFiles(), new File[0])) { String filePath = path + file.getName(); if (file.isDirectory()) { files.put(filePath, file); listFilesRecursive(filePath, files, file); } else { files.put(filePath, file); } } } public static void unpackConfig(ByteSource inputSupplier, File outputDir) throws IOException { ZipInputStream in = new ZipInputStream(inputSupplier.openStream()); try { for (ZipEntry zipEntry = in.getNextEntry(); zipEntry != null; zipEntry = in.getNextEntry()) { File file = new File(outputDir, zipEntry.getName()); if (zipEntry.getName().endsWith("/")) { // this is a directory file.mkdirs(); } else { file.getParentFile().mkdirs(); Files.asByteSink(file).writeFrom(in); file.setLastModified(zipEntry.getTime()); } } } finally { in.close(); } } public static ByteSource newConfigEntrySupplier(Repository repository, String config, final String entryName) { URI uri = repository.configToHttpUri(config); if (uri == null) { return null; } URL configUrl; try { configUrl = uri.toURL(); } catch (MalformedURLException e) { throw new RuntimeException("Invalid config bundle location " + uri); } return ConfigUtils.newConfigEntrySupplier(Resources.asByteSource(configUrl), entryName); } public static ByteSource newConfigEntrySupplier(final URI configBundle, String entryName) { ByteSource configBundleInput = new ByteSource() { @Override public InputStream openStream() throws IOException { URL url = configBundle.toURL(); URLConnection connection = url.openConnection(); if (connection instanceof HttpURLConnection) { HttpURLConnection httpConnection = (HttpURLConnection) connection; httpConnection.addRequestProperty("User-Agent", "User-Agent: Apache-Maven/3.0.3 (Java 1.6.0_29; Mac OS X 10.7.2)"); } InputStream in = connection.getInputStream(); return in; } }; return newConfigEntrySupplier(configBundleInput, entryName); } private static ByteSource newConfigEntrySupplier(final ByteSource configBundle, final String entryName) { return new ByteSource() { @Override public InputStream openStream() throws IOException { boolean success = false; ZipInputStream in = new ZipInputStream(configBundle.openStream()); try { ZipEntry zipEntry = in.getNextEntry(); while (zipEntry != null && !zipEntry.getName().equals(entryName)) { zipEntry = in.getNextEntry(); } if (zipEntry == null) { throw new FileNotFoundException(entryName); } // wrap with buffer to make it difficult to mess with the zip stream BufferedInputStream stream = new BufferedInputStream(in); success = true; return stream; } finally { if (!success) { in.close(); } } } }; } public static ConfigurationFactory createConfigurationFactory(Repository repository, String config) { URI configUri = repository.configToHttpUri(config); if (configUri == null) { throw new RuntimeException("Unknown configuration: " + config); } try { Properties properties = loadProperties(newConfigEntrySupplier(configUri, CONFIG_PROPERTIES)); return new ConfigurationFactory(Maps.fromProperties(properties)); } catch (IOException e) { String message = format("Unable to read %s from config %s: %s", CONFIG_PROPERTIES, config, e.getMessage()); throw new RuntimeException(message, e); } } private static Properties loadProperties(ByteSource inputSupplier) throws IOException { Properties properties = new Properties(); try (InputStream input = inputSupplier.openStream()) { properties.load(input); } return properties; } }