/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.server.service.servicemanager.configuration.yaml; import com.foundationdb.server.service.servicemanager.configuration.BindingsConfigurationLoader; import com.foundationdb.server.service.servicemanager.configuration.ServiceConfigurationHandler; import com.foundationdb.util.Enumerated; import com.foundationdb.util.EnumeratingIterator; import com.google.inject.Module; import org.yaml.snakeyaml.Yaml; import java.io.Reader; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @SuppressWarnings("unused") public final class YamlConfiguration implements BindingsConfigurationLoader { // BindingsConfigurationLoader interface @Override public void loadInto(ServiceConfigurationHandler config) { try { Yaml parser = new Yaml(); for (Enumerated<Object> enumerated : EnumeratingIterator.of(parser.loadAll(source))) { Object item = enumerated.get(); if ( ! (item instanceof List) ) { throw new BadConfigurationException("block " + enumerated.count() + " of " + sourceName, item); } readBlock(config, enumerated.count(), (List<?>)item); } config.sectionEnd(); } catch (BadConfigurationException e) { config.unrecognizedCommand(e.getWhere(), e.getObject(), e.getMessage()); } } public YamlConfiguration(String sourceName, Reader source, ClassLoader classLoader) { this.sourceName = sourceName; this.source = source; this.classLoader = classLoader; } // private methods private void readBlock(ServiceConfigurationHandler config, int blockId, List<?> commands) { for (Enumerated<?> enumerated : EnumeratingIterator.of(commands)) { Object elem = enumerated.get(); if ( !(elem instanceof Map)) { throw new BadConfigurationException("block " + blockId + " item " + enumerated.count(), elem); } String where = "block " + blockId + " command " + enumerated.count(); Map<?,?> commandMap = (Map<?,?>) elem; if (commandMap.size() != 1) { throw new BadConfigurationException(where, commandMap, "command map needs to have size 1"); } Map.Entry<?,?> commandEntry = commandMap.entrySet().iterator().next(); Object commandKey = commandEntry.getKey(); if (! (commandKey instanceof String)) { throw new BadConfigurationException(where, commandMap, "command key needs to be a string"); } Command command = whichCommand(where, (String)commandKey); Object commandValue = commandEntry.getValue(); switch (command) { case BIND: internalDoBind( config, stringsMap(where, commandValue) ); break; case BIND_AND_LOCK: internalDoBindAndLock(config, stringsMap(where, commandValue)); break; case BIND_MODULES: internalDoBindModules(config, where, commandValue); break; case LOCK: internalDoLock( config, strings(where, commandValue) ); break; case REQUIRE: internalDoRequire( config, strings(where, commandValue) ); break; case REQUIRE_LOCKED: internalDoRequireLocked( config, strings(where, commandValue) ); break; case LOCKED: internalDoLocked( config, strings(where, commandValue) ); break; case BOUND: internalDoBound( config, strings(where, commandValue) ); break; case PRIORITIZE: internalDoPrioritize( config, strings(where, commandValue) ); break; case UNBIND: internalDoUnbind( config, strings(where, commandValue) ); break; default: throw new UnsupportedOperationException(command.name()); } } } private void internalDoBind(ServiceConfigurationHandler config, Map<String,String> bindings) { for(Map.Entry<String,String> binding : bindings.entrySet()) { config.bind(binding.getKey(), binding.getValue(), classLoader); } } private void internalDoBindAndLock(ServiceConfigurationHandler config, Map<String,String> bindings) { for(Map.Entry<String,String> binding : bindings.entrySet()) { config.bind(binding.getKey(), binding.getValue(), classLoader); config.lock(binding.getKey()); } } private void internalDoBindModules(ServiceConfigurationHandler config, String where, Object commandValue) { List<String> moduleNames = strings(where, commandValue); List<Module> modules = new ArrayList<>(moduleNames.size()); ClassLoader localClassLoader = (classLoader == null) ? ClassLoader.getSystemClassLoader() : classLoader; for (String moduleName : moduleNames) { try { Class<?> cls = localClassLoader.loadClass(moduleName); Object module = cls.newInstance(); if (module instanceof Module) modules.add((Module)module); else config.bindModulesError(where, commandValue, "bind-modules includes non-Module: " + cls); } catch (Exception e) { config.bindModulesError(where, commandValue, "error during bind-modules command: " + e); } } config.bindModules(modules); } private void internalDoLock(ServiceConfigurationHandler config, List<String> interfaceNames) { for (String interfaceName : interfaceNames) { config.lock(interfaceName); } } private void internalDoRequire(ServiceConfigurationHandler config, List<String> interfaceNames) { for (String interfaceName : interfaceNames) { config.require(interfaceName); } } private void internalDoRequireLocked(ServiceConfigurationHandler config, List<String> interfaceNames) { for (String interfaceName : interfaceNames) { config.require(interfaceName); config.mustBeLocked(interfaceName); } } private void internalDoLocked(ServiceConfigurationHandler config, List<String> interfaceNames) { for (String interfaceName : interfaceNames) { config.mustBeLocked(interfaceName); } } private void internalDoBound(ServiceConfigurationHandler config, List<String> interfaceNames) { for (String interfaceName : interfaceNames) { config.mustBeBound(interfaceName); } } private void internalDoPrioritize(ServiceConfigurationHandler config, List<String> interfaceNames) { for (String interfaceName : interfaceNames) { config.prioritize(interfaceName); } } private void internalDoUnbind(ServiceConfigurationHandler config, List<String> interfaceNames) { for (String interfaceName : interfaceNames) { config.unbind(interfaceName); } } private static Command whichCommand(String where, String commandName) { commandName = commandName.toUpperCase().replace(' ', '_').replace('-', '_'); try { return Command.valueOf(commandName); } catch (IllegalArgumentException e) { throw new BadConfigurationException(where, commandName); } } private static List<String> strings(String where, Object object) { if (object instanceof String) { return Collections.singletonList((String)object); } if (object instanceof List) { List<?> wildList = (List<?>) object; for (Object elem : wildList) { if (!(elem instanceof String)) { throw new BadConfigurationException(where, object, "required a String or List<String>"); } } return launderStringsList(wildList); } throw new BadConfigurationException(where, object, "required a String or List<String>"); } private static Map<String,String> stringsMap(String where, Object object) { if (! (object instanceof Map) ) { throw new BadConfigurationException(where, object, "required a Map<String,String>"); } Map<?,?> wildMap = (Map<?,?>) object; for (Map.Entry<?,?> entry : wildMap.entrySet()) { if (! (entry.getKey() instanceof String) || ! (entry.getValue() instanceof String) ) { throw new BadConfigurationException(where, object, "required a Map<String,String>"); } } return launderStringsMap(wildMap); } /** * This method is simply here to isolate the unchecked warning suppression. * @param incoming the map to cast * @return the incoming map, casted */ @SuppressWarnings("unchecked") private static Map<String,String> launderStringsMap(Map<?,?> incoming) { return (Map<String,String>) incoming; } /** * This method is simply here to isolate the unchecked warning suppression. * @param incoming the list to cast * @return the incoming list, casted */ @SuppressWarnings("unchecked") private static List<String> launderStringsList(List<?> incoming) { return (List<String>) incoming; } // internal state final String sourceName; final Reader source; final ClassLoader classLoader; // nested classes private enum Command { BIND, BIND_AND_LOCK, BIND_MODULES, LOCK, REQUIRE, REQUIRE_LOCKED, LOCKED, BOUND, PRIORITIZE, UNBIND, ; } private static class BadConfigurationException extends RuntimeException { BadConfigurationException(String where, Object object) { this(where, object, "unknown error"); } BadConfigurationException(String where, Object object, String message) { super(message); this.where = where; this.object = object; this.message = message; } String getWhere() { return where; } Object getObject() { return object; } private final String where; private final Object object; private final String message; } }