package com.threatconnect.apps.playbooks.test.config; import com.threatconnect.app.addons.util.config.InvalidJsonFileException; import com.threatconnect.app.addons.util.config.install.Install; import com.threatconnect.app.addons.util.config.install.InstallUtil; import com.threatconnect.app.addons.util.config.validation.ValidationException; import com.threatconnect.app.apps.App; import com.threatconnect.app.apps.AppConfig; import com.threatconnect.app.apps.AppExecutor; import com.threatconnect.app.apps.DefaultAppConfig; import com.threatconnect.app.playbooks.app.PlaybooksApp; import com.threatconnect.app.playbooks.app.PlaybooksAppConfig; import com.threatconnect.app.playbooks.db.DBServiceFactory; import com.threatconnect.apps.playbooks.test.db.EmbeddedMapDBService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author Greg Marut */ public class PlaybooksTestConfiguration { public static final Pattern PATTERN_INSTALL_JSON = Pattern.compile("^(?:(.*)\\.)?install\\.json$"); private static final Logger logger = LoggerFactory.getLogger(PlaybooksTestConfiguration.class); //holds the instance to this singleton private static PlaybooksTestConfiguration instance; private static final Object lock = new Object(); //holds the playbook configuration map private final Map<Class<? extends PlaybooksApp>, PlaybookConfig> configurationMap; //holds the default app config object private final AppConfig globalAppConfig; private PlaybooksTestConfiguration() { this.configurationMap = new HashMap<Class<? extends PlaybooksApp>, PlaybookConfig>(); this.globalAppConfig = new DefaultAppConfig(); loadConfigurationFiles(); } public void loadConfigurationFiles() { logger.info("Loading Playbooks Test Configuration"); //find all of the install.json files List<File> files = findInstallJsonFiles(); logger.info("Found {} install.json files", files.size()); //for each of the files for (File file : files) { loadFileAndConfigure(file); } } public void registerEmbeddedDBService() { logger.info("Registering EmbeddedMapDBService as default database"); //register the inmemory database DBServiceFactory.registerCustomDBService("Memory", new EmbeddedMapDBService()); globalAppConfig.set(PlaybooksAppConfig.PARAM_DB_TYPE, "Memory"); } public void setGlobalAppParam(final String name, final String value) { globalAppConfig.set(name, value); } private List<File> findInstallJsonFiles() { //holds the list of files to return List<File> files = new ArrayList<File>(); // retrieve the base directory file final File root = new File("./"); // for each file in the root for (File file : root.listFiles()) { // make sure this is a file (not a directory) if (file.isFile()) { // retrieve the matcher for this filename Matcher matcher = PATTERN_INSTALL_JSON.matcher(file.getName()); // make sure this is an install file if (matcher.matches()) { files.add(file); } } } return files; } public void loadFileAndConfigure(final File file) { try { logger.info("Loading {}", file.getAbsolutePath()); //read the json file Install install = InstallUtil.load(file); //check to see if this is a playbooks app if (install.isPlaybookApp()) { //configure the app for this install.json file PlaybookConfig playbookConfig = buildPlaybookConfig(install, file); registerPlaybookConfig(playbookConfig); } else { logger.info("Skipping \"{}\" -- runtimeLevel indicates this config is not a playbooks app ", file.getAbsolutePath()); } } catch (UnsupposedPlaybookMainClassException | IOException | InvalidJsonFileException | ValidationException | InvalidPlaybookAppException e) { throw new PlaybooksConfigurationException(e); } } /** * Reads an install.json file and configures the playbooks app according to the config * * @param install * @throws InvalidJsonFileException * @throws ClassNotFoundException * @throws UnsupposedPlaybookMainClassException */ private PlaybookConfig buildPlaybookConfig(final Install install, final File file) throws InvalidJsonFileException, UnsupposedPlaybookMainClassException, InvalidPlaybookAppException { try { //get the program main class @SuppressWarnings("unchecked") Class<? extends AppExecutor> programMainClass = (Class<? extends AppExecutor>) Class.forName(install.getProgramMain()); //instantiate this class AppExecutor appExecutor = programMainClass.newInstance(); //retrieve the classes that are executed from this main AppConfig appConfig = new DefaultAppConfig().copyFrom(globalAppConfig); Class<? extends App> appClass = appExecutor.getAppClassToExecute(appConfig); //ensure that this class has a declared static main method Method method = appExecutor.getClass().getDeclaredMethod("main", String[].class); if (!Modifier.isStatic(method.getModifiers())) { throw new UnsupposedPlaybookMainClassException( install.getProgramMain() + " must have static main method."); } //configure this app return buildPlaybookConfig(appClass, install, file); } catch (ClassNotFoundException e) { throw new UnsupposedPlaybookMainClassException( install.getProgramMain() + " could not be found."); } catch (InstantiationException | IllegalAccessException e) { throw new UnsupposedPlaybookMainClassException( install.getProgramMain() + " must have a public no-arg constructor."); } catch (ClassCastException e) { throw new UnsupposedPlaybookMainClassException(install.getProgramMain() + " is not supported. Only classes that implement " + AppExecutor.class.getName() + " are currently supported."); } catch (NoSuchMethodException e) { throw new UnsupposedPlaybookMainClassException( install.getProgramMain() + " must have static main method."); } } private PlaybookConfig buildPlaybookConfig(final Class<? extends App> appClass, final Install install, final File file) throws InvalidPlaybookAppException { logger.info("Configuring playbook \"{}\", loaded from file \"{}\"", appClass.getName(), file.getAbsolutePath()); //make sure this app is a playbooks app if (PlaybooksApp.class.isAssignableFrom(appClass)) { //cast the class Class<? extends PlaybooksApp> playbooksAppClass = (Class<? extends PlaybooksApp>) appClass; //create a new playbook configuration class return new PlaybookConfig(playbooksAppClass, install); } else { throw new InvalidPlaybookAppException( appClass.getName() + ", loaded from " + file.getAbsolutePath() + ", must extend from " + PlaybooksApp.class.getName()); } } private void registerPlaybookConfig(final PlaybookConfig playbookConfig) { //make sure this configuration does not already exist if (!configurationMap.containsKey(playbookConfig.getPlaybookAppClass())) { //add this config to the map configurationMap.put(playbookConfig.getPlaybookAppClass(), playbookConfig); } else { //warn that this class was already loaded logger .warn(playbookConfig.getPlaybookAppClass().getName() + " already configured. Skipping configuration."); } } /** * Creates a builder object for dynamically registering a playbook app config without a json file * * @param playbookAppClass * @return */ public PlaybookConfigBuilder createPlaybookConfigBuilder(final Class<? extends PlaybooksApp> playbookAppClass) { return new PlaybookConfigBuilder(playbookAppClass, this); } public PlaybookConfigBuilder createPlaybookConfigBuilder(final File file) { try { logger.info("Loading {}", file.getAbsolutePath()); //read the json file Install install = InstallUtil.load(file); //check to see if this is a playbooks app if (install.isPlaybookApp()) { //configure the app for this install.json file PlaybookConfig playbookConfig = buildPlaybookConfig(install, file); return new PlaybookConfigBuilder(playbookConfig, this); } else { throw new PlaybooksConfigurationException("Skipping \"" + file.getAbsolutePath() + "\" -- runtimeLevel indicates this config is not a playbooks app "); } } catch (UnsupposedPlaybookMainClassException | IOException | InvalidJsonFileException | ValidationException | InvalidPlaybookAppException e) { throw new PlaybooksConfigurationException(e); } } /** * Adds a playbook configuration to the map and overrides any existing configuration * * @param playbookConfig */ void registerDynamicPlaybookConfiguration(final PlaybookConfig playbookConfig) { logger.info("Registering dynamic PlaybookConfig for \"{}\"", playbookConfig.getPlaybookAppClass().getName()); //check to see if a playbooks configuration already exists for this class if (configurationMap.containsKey(playbookConfig.getPlaybookAppClass())) { //notify the user via a warning logger.warn("Overriding existing configuration for \"{}\"", playbookConfig.getPlaybookAppClass().getName()); } //add this config to the map configurationMap.put(playbookConfig.getPlaybookAppClass(), playbookConfig); } public Map<Class<? extends PlaybooksApp>, PlaybookConfig> getConfigurationMap() { return new HashMap<Class<? extends PlaybooksApp>, PlaybookConfig>(configurationMap); } public AppConfig getGlobalAppConfig() { return globalAppConfig; } public static PlaybooksTestConfiguration getInstance() { //check to see if the instance is null if (null == instance) { //acquire a thread lock on this object to prevent the object from building twice synchronized (lock) { //now that a lock is in place, check again for null if (null == instance) { instance = new PlaybooksTestConfiguration(); } } } return instance; } }