// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.container.standalone; import com.yahoo.config.ConfigBuilder; import com.yahoo.config.ConfigInstance; import com.yahoo.config.subscription.ConfigInterruptedException; import com.yahoo.config.subscription.SubscriberClosedException; import com.yahoo.container.di.config.Subscriber; import com.yahoo.container.di.config.SubscriberFactory; import com.yahoo.vespa.config.ConfigKey; import com.yahoo.vespa.model.VespaModel; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @author Tony Vaagenes * @author gjoranv * @author ollivir */ public class StandaloneSubscriberFactory implements SubscriberFactory { private final VespaModel root; public StandaloneSubscriberFactory(VespaModel root) { this.root = root; } private class StandaloneSubscriber implements Subscriber { private final Set> configKeys; private long generation = -1L; private volatile boolean shutdown = false; StandaloneSubscriber(Set> configKeys) { this.configKeys = configKeys; } @Override public boolean configChanged() { return generation == 0; } @Override public void close() { shutdown = true; } @Override public Map, ConfigInstance> config() { Map, ConfigInstance> ret = new HashMap<>(); for (ConfigKey key : configKeys) { ConfigInstance.Builder builder = root.getConfig(newBuilderInstance(key), key.getConfigId()); if (builder == null) throw new RuntimeException("Invalid config id " + key.getConfigId()); ret.put(key, newConfigInstance(builder)); } return ret; } @Override public long waitNextGeneration(boolean isInitializing) { generation++; if (generation != 0) { try { while (!shutdown && !Thread.interrupted()) { Thread.sleep(100); } if (shutdown) throw new SubscriberClosedException(); } catch (InterruptedException e) { throw new ConfigInterruptedException(e); } } return generation; } // if waitNextGeneration has not yet been called, -1 should be returned @Override public long generation() { return generation; } } @Override @SuppressWarnings("unchecked") public Subscriber getSubscriber(Set> configKeys, String name) { return new StandaloneSubscriber((Set>) configKeys); } public void reloadActiveSubscribers(long generation) { throw new RuntimeException("unsupported"); } private static ConfigInstance.Builder newBuilderInstance(ConfigKey key) { try { return builderClass(key).getDeclaredConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException("ConfigInstance builder cannot be instantiated", e); } } @SuppressWarnings("unchecked") private static Class builderClass(ConfigKey key) { Class configClass = key.getConfigClass(); if (configClass != null) { Class[] nestedClasses = configClass.getClasses(); for (Class clazz : nestedClasses) { if (clazz.getName().equals(key.getConfigClass().getName() + "$Builder")) { return (Class) clazz; } } } throw new RuntimeException("Builder class for " + (configClass == null ? null : configClass.getName()) + " could not be located"); } private static ConfigInstance newConfigInstance(ConfigBuilder builder) { try { return configClass(builder).getConstructor(builder.getClass()).newInstance(builder); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException("ConfigInstance cannot be instantiated", e); } } @SuppressWarnings("unchecked") private static Class configClass(ConfigBuilder builder) { return (Class) builder.getClass().getEnclosingClass(); } @Override public void close() {} }