// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.messagebus.routing; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Along with the {@link RoutingTableSpec}, {@link RouteSpec} and {@link HopSpec}, this holds the routing specifications * for all protocols. The only way a client can configure or alter the settings of a message bus instance is through * these classes. *

* This class is the root spec class for configuring message bus routing. * * @author Simon Thoresen Hult */ public class RoutingSpec { private final List tables = new ArrayList<>(); private final boolean verify; /** * Creates an empty specification. */ public RoutingSpec() { this(true); } /** * Creates an empty specification. * * @param verify Whether this should be verified. */ public RoutingSpec(boolean verify) { this.verify = verify; } /** * Implements the copy constructor. * * @param spec the spec to copy. */ public RoutingSpec(RoutingSpec spec) { verify = spec.verify; for (RoutingTableSpec table : spec.tables) { tables.add(new RoutingTableSpec(table)); } } /** * Returns whether or not there are routing table specs contained in this. * * @return True if there is at least one table. */ public boolean hasTables() { return !tables.isEmpty(); } /** * Returns the number of routing table specs that are contained in this. * * @return The number of routing tables. */ public int getNumTables() { return tables.size(); } /** * Returns the routing table spec at the given index. * * @param i The index of the routing table to return. * @return The routing table at the given index. */ public RoutingTableSpec getTable(int i) { return tables.get(i); } /** * Sets the routing table spec at the given index. * * @param i The index at which to set the routing table. * @param table The routing table to set. * @return This, to allow chaining. */ public RoutingSpec setTable(int i, RoutingTableSpec table) { tables.set(i, table); return this; } /** * Adds a routing table spec to the list of tables. * * @param table The routing table to add. * @return This, to allow chaining. */ public RoutingSpec addTable(RoutingTableSpec table) { tables.add(table); return this; } /** * Returns the routing table spec at the given index. * * @param i The index of the routing table to remove. * @return The removed routing table. */ public RoutingTableSpec removeTable(int i) { return tables.remove(i); } /** * Clears the list of routing table specs contained in this. * * @return This, to allow chaining. */ public RoutingSpec clearTables() { tables.clear(); return this; } /** * Verifies the content of this against the given application. * * @param app The application to verify against. * @param errors The list of errors found. * @return True if no errors where found. */ public boolean verify(ApplicationSpec app, List errors) { if (verify) { Map tableNames = new HashMap<>(); for (RoutingTableSpec table : tables) { String name = table.getProtocol(); int count = tableNames.containsKey(name) ? tableNames.get(name) : 0; tableNames.put(name, count + 1); table.verify(app, errors); } for (Map.Entry entry : tableNames.entrySet()) { int count = entry.getValue(); if (count > 1) { errors.add("Routing table '" + entry.getKey() + "' is defined " + count + " times."); } } } return errors.isEmpty(); } /** * Appends the content of this to the given config string builder. * * @param cfg The config to add to. * @param prefix The prefix to use for each add. */ public void toConfig(StringBuilder cfg, String prefix) { int numTables = tables.size(); if (numTables > 0) { cfg.append(prefix).append("routingtable[").append(numTables).append("]\n"); for (int i = 0; i < numTables; ++i) { tables.get(i).toConfig(cfg, prefix + "routingtable[" + i + "]."); } } } /** * Convert a string value to a quoted value suitable for use in a config string. *

* Adds double quotes before and after, and adds backslash-escapes to any double quotes that was contained in the * string. A null pointer will produce the special unquoted string null that the config library will convert back * to a null pointer. * * @param input the String to be escaped * @return an escaped String */ static String toConfigString(String input) { if (input == null) { return "null"; } StringBuilder ret = new StringBuilder(2 + input.length()); ret.append("\""); for (int i = 0, len = input.length(); i < len; ++i) { if (input.charAt(i) == '\\') { ret.append("\\\\"); } else if (input.charAt(i) == '"') { ret.append("\\\""); } else if (input.charAt(i) == '\n') { ret.append("\\n"); } else if (input.charAt(i) == 0) { ret.append("\\x00"); } else { ret.append(input.charAt(i)); } } ret.append("\""); return ret.toString(); } @Override public String toString() { StringBuilder ret = new StringBuilder(); toConfig(ret, ""); return ret.toString(); } @Override public boolean equals(Object obj) { if (!(obj instanceof RoutingSpec)) { return false; } RoutingSpec rhs = (RoutingSpec)obj; if (!tables.equals(rhs.tables)) { return false; } return true; } @Override public int hashCode() { int result = tables.hashCode(); result = 31 * result + (verify ? 1 : 0); return result; } }