/** * Copyright 2015 StreamSets Inc. * * Licensed under 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 com.streamsets.pipeline; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.instrument.Instrumentation; import java.lang.reflect.Method; import java.net.URL; import java.security.AccessControlException; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; public class BootstrapMain { private static final String PIPELINE_BOOTSTRAP_DEBUG_SYS_PROP = "streamsets.bootstrap.debug"; public static final String PIPELINE_BOOTSTRAP_CLASSLOADER_SYS_PROP = "streamsets.classloader.debug"; private static final String STREAMSETS_LIBRARIES_EXTRA_DIR_SYS_PROP = "STREAMSETS_LIBRARIES_EXTRA_DIR"; private static final String MAIN_CLASS_OPTION = "-mainClass"; private static final String API_CLASSPATH_OPTION = "-apiClasspath"; private static final String CONTAINER_CLASSPATH_OPTION = "-containerClasspath"; private static final String STREAMSETS_LIBRARIES_DIR_OPTION = "-streamsetsLibrariesDir"; private static final String STREAMSETS_LIBRARIES_EXTRA_DIR_OPTION = "-streamsetsLibrariesExtraDir"; private static final String USER_LIBRARIES_DIR_OPTION = "-userLibrariesDir"; private static final String LIBS_COMMON_LIB_DIR_OPTION = "-libsCommonLibDir"; private static final String CONFIG_DIR_OPTION = "-configDir"; private static final String SET_CONTEXT_METHOD = "setContext"; private static final String MAIN_METHOD = "main"; private static final String STAGE_LIB_JARS_DIR = "lib"; private static final String STAGE_LIB_CONF_DIR = "etc"; private static final String JARS_WILDCARD = "*.jar"; public static final String FILE_SEPARATOR = System.getProperty("file.separator"); public static final String CLASSPATH_SEPARATOR = System.getProperty("path.separator"); private static final String MISSING_ARG_MSG = "Missing argument for '%s'"; private static final String INVALID_OPTION_ARG_MSG = "Invalid option or argument '%s'"; private static final String OPTION_NOT_SPECIFIED_MSG = "Option not specified '%s <ARG>'"; private static final String MISSING_STAGE_LIB_JARS_DIR_MSG = "Invalid library '%s', missing lib directory"; private static final String MISSING_STAGE_LIBRARIES_DIR_MSG = "Stage libraries directory '%s' does not exist"; private static final String CLASSPATH_DIR_DOES_NOT_EXIST_MSG = "Classpath directory '%s' does not exist"; private static final String CLASSPATH_PATH_S_IS_NOT_A_DIR_MSG = "Specified Classpath path '%s' is not a directory"; static final String SDC_CONFIG_FILE = "sdc.properties"; static final String WHITE_LIST_FILE = "stagelibswhitelist.properties"; static final String SYSTEM_LIBS_WHITE_LIST_KEY = "system.stagelibs.whitelist"; static final String USER_LIBS_WHITE_LIST_KEY = "user.stagelibs.whitelist"; static final String SYSTEM_LIBS_BLACK_LIST_KEY = "system.stagelibs.blacklist"; static final String USER_LIBS_BLACK_LIST_KEY = "user.stagelibs.blacklist"; static final String ALL_VALUES = "*"; private static final String WHITE_LIST_PROPERTY_MISSING_MSG = "WhiteList property '%s' is missing in in file '%s'"; private static final String WHITE_LIST_COULD_NOT_LOAD_FILE_MSG = "Could not load WhiteList file '%s': %s"; private static final String DEBUG_MSG_PREFIX = "DEBUG: "; private static final String WARN_MSG_PREFIX = "WARN : "; private static final String DEBUG_MSG = DEBUG_MSG_PREFIX + "'%s' %s"; public static final String WARN_MSG = WARN_MSG_PREFIX + "'%s' %s"; private static Instrumentation instrumentation; public static Instrumentation getInstrumentation() { return instrumentation; } /** * Visible due to JVM requirements only */ public static void premain(String args, Instrumentation instrumentation) { if (BootstrapMain.instrumentation == null) { BootstrapMain.instrumentation = instrumentation; } else { throw new IllegalStateException("Premain method cannot be called twice (" + BootstrapMain.instrumentation + ")"); } } @SuppressWarnings("unchecked") public static void main(String[] args) throws Exception { try { System.getProperty("test.to.ensure.security.is.configured.correctly"); } catch (AccessControlException e) { String msg = "Error: Security is enabled but sdc policy file is misconfigured"; throw new IllegalArgumentException(msg, e); } SDCClassLoader.setDebug(Boolean.getBoolean(PIPELINE_BOOTSTRAP_CLASSLOADER_SYS_PROP)); String mainClass = null; String apiClasspath = null; String containerClasspath = null; String streamsetsLibrariesDir = null; String streamsetsLibrariesExtraDir = null; String userLibrariesDir = null; String configDir = null; String libsCommonLibDir = null; for (int i = 0; i < args.length; i++) { if (args[i].equals(MAIN_CLASS_OPTION)) { i++; if (i < args.length) { mainClass = args[i]; } else { throw new IllegalArgumentException(String.format(MISSING_ARG_MSG, MAIN_CLASS_OPTION)); } } else if (args[i].equals(API_CLASSPATH_OPTION)) { i++; if (i < args.length) { apiClasspath = args[i]; } else { throw new IllegalArgumentException(String.format(MISSING_ARG_MSG, API_CLASSPATH_OPTION)); } } else if (args[i].equals(CONTAINER_CLASSPATH_OPTION)) { i++; if (i < args.length) { containerClasspath = args[i]; } else { throw new IllegalArgumentException(String.format(MISSING_ARG_MSG, CONTAINER_CLASSPATH_OPTION)); } } else if (args[i].equals(STREAMSETS_LIBRARIES_DIR_OPTION)) { i++; if (i < args.length) { streamsetsLibrariesDir = args[i]; } else { throw new IllegalArgumentException(String.format(MISSING_ARG_MSG, STREAMSETS_LIBRARIES_DIR_OPTION)); } } else if (args[i].equals(STREAMSETS_LIBRARIES_EXTRA_DIR_OPTION)) { i++; if (i < args.length) { streamsetsLibrariesExtraDir = args[i]; } else { throw new IllegalArgumentException(String.format(MISSING_ARG_MSG, STREAMSETS_LIBRARIES_EXTRA_DIR_OPTION)); } } else if (args[i].equals(USER_LIBRARIES_DIR_OPTION)) { i++; if (i < args.length) { userLibrariesDir = args[i]; } else { throw new IllegalArgumentException(String.format(MISSING_ARG_MSG, USER_LIBRARIES_DIR_OPTION)); } } else if (args[i].equals(CONFIG_DIR_OPTION)) { i++; if (i < args.length) { configDir = args[i]; } else { throw new IllegalArgumentException(String.format(MISSING_ARG_MSG, USER_LIBRARIES_DIR_OPTION)); } } else if (args[i].equals(LIBS_COMMON_LIB_DIR_OPTION)) { i++; if (i < args.length) { libsCommonLibDir = args[i]; } else { throw new IllegalArgumentException(String.format(MISSING_ARG_MSG, USER_LIBRARIES_DIR_OPTION)); } } else { throw new IllegalArgumentException(String.format(INVALID_OPTION_ARG_MSG, args[i])); } } if (mainClass == null) { throw new IllegalArgumentException(String.format(OPTION_NOT_SPECIFIED_MSG, MAIN_CLASS_OPTION)); } if (apiClasspath == null) { throw new IllegalArgumentException(String.format(OPTION_NOT_SPECIFIED_MSG, API_CLASSPATH_OPTION)); } if (containerClasspath == null) { throw new IllegalArgumentException(String.format(OPTION_NOT_SPECIFIED_MSG, CONTAINER_CLASSPATH_OPTION)); } if (streamsetsLibrariesDir == null) { throw new IllegalArgumentException(String.format(OPTION_NOT_SPECIFIED_MSG, STREAMSETS_LIBRARIES_DIR_OPTION)); } if (userLibrariesDir == null) { throw new IllegalArgumentException(String.format(OPTION_NOT_SPECIFIED_MSG, USER_LIBRARIES_DIR_OPTION)); } if (configDir == null) { throw new IllegalArgumentException(String.format(OPTION_NOT_SPECIFIED_MSG, CONFIG_DIR_OPTION)); } boolean debug = Boolean.getBoolean(PIPELINE_BOOTSTRAP_DEBUG_SYS_PROP); if (debug) { System.out.println(DEBUG_MSG_PREFIX); System.out.println(String.format(DEBUG_MSG, CONFIG_DIR_OPTION, configDir)); System.out.println(DEBUG_MSG_PREFIX); System.out.println(String.format(DEBUG_MSG, MAIN_CLASS_OPTION, mainClass)); System.out.println(DEBUG_MSG_PREFIX); System.out.println(String.format(DEBUG_MSG, API_CLASSPATH_OPTION, apiClasspath)); System.out.println(DEBUG_MSG_PREFIX); System.out.println(String.format(DEBUG_MSG, CONTAINER_CLASSPATH_OPTION, containerClasspath)); System.out.println(DEBUG_MSG_PREFIX); System.out.println(String.format(DEBUG_MSG, STREAMSETS_LIBRARIES_DIR_OPTION, streamsetsLibrariesDir)); System.out.println(DEBUG_MSG_PREFIX); System.out.println(String.format(DEBUG_MSG, STREAMSETS_LIBRARIES_EXTRA_DIR_OPTION, streamsetsLibrariesExtraDir)); System.out.println(DEBUG_MSG_PREFIX); System.out.println(String.format(DEBUG_MSG, USER_LIBRARIES_DIR_OPTION, userLibrariesDir)); } // Extract classpath URLs for API, Container and Stage Libraries List<URL> apiUrls = getClasspathUrls(apiClasspath); List<URL> containerUrls = getClasspathUrls(containerClasspath); Set<String> systemStageLibs; Set<String> userStageLibs; if (isDeprecatedWhiteListConfiguration(configDir)) { System.out.println(String.format( WARN_MSG, "Using deprecated stage library whitelist configuration file", WHITE_LIST_FILE )); systemStageLibs = getWhiteList(configDir, SYSTEM_LIBS_WHITE_LIST_KEY); userStageLibs = getWhiteList(configDir, USER_LIBS_WHITE_LIST_KEY); } else { systemStageLibs = getSystemStageLibs(configDir); userStageLibs = getUserStageLibs(configDir); } if (debug) { String whiteListStr = (systemStageLibs == null) ? ALL_VALUES : systemStageLibs.toString(); System.out.println(String.format(DEBUG_MSG, "System stage libs", whiteListStr)); whiteListStr = (userStageLibs == null) ? ALL_VALUES : userStageLibs.toString(); System.out.println(String.format(DEBUG_MSG, "User stage libs", whiteListStr)); } Map<String, List<URL>> streamsetsLibsUrls = getStageLibrariesClasspaths(streamsetsLibrariesDir, streamsetsLibrariesExtraDir, systemStageLibs, libsCommonLibDir); Map<String, List<URL>> userLibsUrls = getStageLibrariesClasspaths(userLibrariesDir, null, systemStageLibs, libsCommonLibDir); if (debug) { System.out.println(DEBUG_MSG_PREFIX); System.out.println(String.format(DEBUG_MSG, "API classpath : ", apiUrls)); System.out.println(DEBUG_MSG_PREFIX); System.out.println(String.format(DEBUG_MSG, "Container classpath :", containerUrls)); System.out.println(DEBUG_MSG_PREFIX); System.out.println(String.format(DEBUG_MSG, "StreamSets libraries classpath:", streamsetsLibsUrls)); System.out.println(DEBUG_MSG_PREFIX); System.out.println(String.format(DEBUG_MSG, "User libraries classpath:", userLibsUrls)); System.out.println(DEBUG_MSG_PREFIX); } Map<String, List<URL>> libsUrls = new LinkedHashMap<>(); libsUrls.putAll(streamsetsLibsUrls); libsUrls.putAll(userLibsUrls); // Create all ClassLoaders SDCClassLoader apiCL = SDCClassLoader.getAPIClassLoader(apiUrls, ClassLoader.getSystemClassLoader()); if (debug) { System.out.println(DEBUG_MSG_PREFIX); System.out.print(apiCL.dumpClasspath(DEBUG_MSG_PREFIX)); System.out.println(DEBUG_MSG_PREFIX); } SDCClassLoader containerCL = SDCClassLoader.getContainerCLassLoader(containerUrls, apiCL); if (debug) { System.out.println(DEBUG_MSG_PREFIX); System.out.print(containerCL.dumpClasspath(DEBUG_MSG_PREFIX)); System.out.println(DEBUG_MSG_PREFIX); } List<ClassLoader> stageLibrariesCLs = new ArrayList<>(); for (Map.Entry<String,List<URL>> entry : libsUrls.entrySet()) { String[] parts = entry.getKey().split(FILE_SEPARATOR); if (parts.length != 2) { String msg = "Invalid library name: " + entry.getKey(); throw new IllegalStateException(msg); } String type = parts[0]; String name = parts[1]; SDCClassLoader cl = SDCClassLoader.getStageClassLoader(type, name, entry.getValue(), apiCL); if (debug) { System.out.println(DEBUG_MSG_PREFIX); System.out.print(cl.dumpClasspath(DEBUG_MSG_PREFIX)); System.out.println(DEBUG_MSG_PREFIX); } stageLibrariesCLs.add(cl); } // Bootstrap container Thread.currentThread().setContextClassLoader(containerCL); Class klass = containerCL.loadClass(mainClass); Method method = klass.getMethod(SET_CONTEXT_METHOD, ClassLoader.class, ClassLoader.class, List.class, Instrumentation.class); method.invoke(null, apiCL, containerCL, stageLibrariesCLs, instrumentation); method = klass.getMethod(MAIN_METHOD, String[].class); method.invoke(null, new Object[]{new String[]{}}); } public static Set<String> getSystemStageLibs(String configDir) { return getStageLibs(configDir, SYSTEM_LIBS_WHITE_LIST_KEY, SYSTEM_LIBS_BLACK_LIST_KEY); } public static Set<String> getUserStageLibs(String configDir) { return getStageLibs(configDir, USER_LIBS_WHITE_LIST_KEY, USER_LIBS_BLACK_LIST_KEY); } public static Set<String> getStageLibs(String configDir, String whiteListKey, String blackListKey) { Set<String> stageLibs = null; if (isDeprecatedWhiteListConfiguration(configDir)) { System.out.println(String.format( WARN_MSG, "Using deprecated stage library whitelist configuration file", WHITE_LIST_FILE )); stageLibs = getWhiteList(configDir, whiteListKey); } else { Properties config = readSdcConfiguration(configDir); validateWhiteBlackList(config, whiteListKey, blackListKey); if (config.containsKey(whiteListKey)) { stageLibs = getList(config, whiteListKey, true); } else if (config.containsKey(blackListKey)) { stageLibs = getList(config, blackListKey, false); } } return stageLibs; } // deprecated as of SDC 1.2 // // if whitelist is '*' set is NULL, else whitelist has the whitelisted values public static Set<String> getWhiteList(String configDir, String property) { Set<String> set = null; File whiteListFile = new File(configDir, WHITE_LIST_FILE).getAbsoluteFile(); if (whiteListFile.exists()) { try (InputStream is = new FileInputStream(whiteListFile)) { Properties props = new Properties(); props.load(is); String whiteList = props.getProperty(property); if (whiteList == null) { throw new IllegalArgumentException(String.format(WHITE_LIST_PROPERTY_MISSING_MSG, property, whiteListFile)); } whiteList = whiteList.trim(); if (!whiteList.equals(ALL_VALUES)) { set = new HashSet<>(); for (String name : whiteList.split(",")) { name = name.trim(); if (!name.isEmpty()) { set.add("+" + name.trim()); } } } } catch (IOException ex) { throw new IllegalArgumentException(String.format(WHITE_LIST_COULD_NOT_LOAD_FILE_MSG, whiteListFile, ex.toString()), ex); } } return set; } private static final String CANNOT_READ_SDC_CONFIG_FILE = "File '%s' cannot be read: %s"; private static final String CANNOT_HAVE_WHITE_BLACK_LIST_MSG = "Configuration file '%s' cannot define both '%s' and '%s' properties"; public static boolean isDeprecatedWhiteListConfiguration(String configDir) { return new File(configDir, WHITE_LIST_FILE).getAbsoluteFile().exists(); } public static Properties readSdcConfiguration(String configDir) { File file = new File(configDir, SDC_CONFIG_FILE).getAbsoluteFile(); try (InputStream is = new FileInputStream(file)) { Properties props = new Properties(); props.load(is); return props; } catch (IOException ex) { throw new IllegalArgumentException(String.format(CANNOT_READ_SDC_CONFIG_FILE, file, ex.toString())); } } public static void validateWhiteBlackList(Properties props, String whiteList, String blackList) { if (props.containsKey(whiteList) && props.containsKey(blackList)) { throw new IllegalArgumentException(String.format( CANNOT_HAVE_WHITE_BLACK_LIST_MSG, SDC_CONFIG_FILE, whiteList, blackList )); } } public static Set<String> getList(Properties props, String property, boolean whitelist) { String prefix = (whitelist) ? "+" : "-"; Set<String> set = null; String whiteList = props.getProperty(property, null); if (whiteList != null) { set = new HashSet<>(); for (String name : whiteList.split(",")) { name = name.trim(); if (!name.isEmpty()) { set.add(prefix + name.trim()); } } } return set; } public static FileFilter createStageLibFilter(final Set<String> stageLibs) { return new FileFilter() { @Override public boolean accept(File pathname) { boolean accept = false; if (pathname.isDirectory()) { if (stageLibs == null) { // stageLibs == NULL means ALL accept = true; } else { boolean isWhiteList = stageLibs.iterator().next().startsWith("+"); if (isWhiteList && stageLibs.contains("+" + pathname.getName())) { accept = true; } else if (!isWhiteList && !stageLibs.contains("-" + pathname.getName())) { accept = true; } } } return accept; } }; } // Visible for testing public static Map<String, List<URL>> getStageLibrariesClasspaths(String stageLibrariesDir, String librariesExtraDir, final Set<String> stageLibs, String libsCommonLibDir) throws Exception { Map<String, List<URL>> map = new LinkedHashMap<String, List<URL>>(); File baseDir = new File(stageLibrariesDir).getAbsoluteFile(); if (baseDir.exists()) { File[] libDirs = baseDir.listFiles(createStageLibFilter(stageLibs)); StringBuilder commonLibJars = new StringBuilder(); if (libsCommonLibDir != null) { commonLibJars.append(new File(libsCommonLibDir).getAbsolutePath()).append(FILE_SEPARATOR).append(JARS_WILDCARD). append(CLASSPATH_SEPARATOR); } for (File libDir : libDirs) { File jarsDir = new File(libDir, STAGE_LIB_JARS_DIR); File etc = new File(libDir, STAGE_LIB_CONF_DIR); if (!jarsDir.exists()) { throw new IllegalArgumentException(String.format(MISSING_STAGE_LIB_JARS_DIR_MSG, libDir)); } StringBuilder sb = new StringBuilder(); if (etc.exists()) { sb.append(etc.getAbsolutePath()).append(FILE_SEPARATOR).append(CLASSPATH_SEPARATOR); } sb.append(commonLibJars); sb.append(jarsDir.getAbsolutePath()).append(FILE_SEPARATOR).append(JARS_WILDCARD); // add extralibs if avail if (librariesExtraDir != null) { System.setProperty(STREAMSETS_LIBRARIES_EXTRA_DIR_SYS_PROP, librariesExtraDir); File libExtraDir = new File(librariesExtraDir, libDir.getName()); if (libExtraDir.exists()) { File extraJarsDir = new File(libExtraDir, STAGE_LIB_JARS_DIR); if (extraJarsDir.exists()) { sb.append(CLASSPATH_SEPARATOR).append(extraJarsDir.getAbsolutePath()).append(FILE_SEPARATOR). append(JARS_WILDCARD); } File extraEtc = new File(libExtraDir, STAGE_LIB_CONF_DIR); if (extraEtc.exists()) { sb.append(CLASSPATH_SEPARATOR).append(extraEtc.getAbsolutePath()); } } } map.put(libDir.getParentFile().getName() + FILE_SEPARATOR + libDir.getName(), getClasspathUrls(sb.toString())); } } else { throw new IllegalArgumentException(String.format(MISSING_STAGE_LIBRARIES_DIR_MSG, baseDir)); } return map; } // Visible for testing public static List<URL> getClasspathUrls(String classPath) throws Exception { List<URL> urls = new ArrayList<URL>(); for (String path : classPath.split(CLASSPATH_SEPARATOR)) { if (!path.isEmpty()) { if (path.toLowerCase().endsWith(JARS_WILDCARD)) { path = path.substring(0, path.length() - JARS_WILDCARD.length()); File f = new File(path).getAbsoluteFile(); if (f.exists()) { File[] jars = f.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getName().toLowerCase().endsWith(JARS_WILDCARD.substring(1)); } }); for (File jar : jars) { urls.add(jar.toURI().toURL()); } } else { throw new IllegalArgumentException(String.format(CLASSPATH_DIR_DOES_NOT_EXIST_MSG, f)); } } else { if (!path.endsWith(FILE_SEPARATOR)) { path = path + FILE_SEPARATOR; } File f = new File(path).getAbsoluteFile(); if (f.exists()) { if (f.isDirectory()) { urls.add(f.toURI().toURL()); } else { throw new IllegalArgumentException(String.format(CLASSPATH_PATH_S_IS_NOT_A_DIR_MSG, f)); } } else { throw new IllegalArgumentException(String.format(CLASSPATH_DIR_DOES_NOT_EXIST_MSG, f)); } } } } return urls; } // for test coverage purposes only BootstrapMain() { } }