// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.messagebus.routing;
import com.yahoo.text.Utf8String;
import java.util.*;
/**
* Along with the {@link RoutingSpec}, {@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 contains the spec for a single routing table, which corresponds to exactly one protocol.
*
* @author Simon Thoresen
*/
public class RoutingTableSpec {
private final String protocol;
private final List hops = new ArrayList<>();
private final List routes = new ArrayList<>();
private final boolean verify;
/**
* Creates a new routing table specification for a named protocol.
*
* @param protocol The name of the protocol that this belongs to.
*/
public RoutingTableSpec(String protocol) {
this(protocol, true);
}
/**
* Creates a new routing table specification for a named protocol.
*
* @param protocol The name of the protocol that this belongs to.
*/
public RoutingTableSpec(Utf8String protocol) {
this(protocol.toString(), true);
}
/**
* Creates a new routing table specification for a named protocol.
*
* @param protocol The name of the protocol that this belongs to.
* @param verify Whether or not this should be verified.
*/
public RoutingTableSpec(String protocol, boolean verify) {
this.protocol = protocol;
this.verify = verify;
}
/**
* Implements the copy constructor.
*
* @param obj The object to copy.
*/
public RoutingTableSpec(RoutingTableSpec obj) {
this.protocol = obj.protocol;
this.verify = obj.verify;
for (HopSpec hop : obj.hops) {
hops.add(new HopSpec(hop));
}
for (RouteSpec route : obj.routes) {
routes.add(new RouteSpec(route));
}
}
/**
* Returns the name of the protocol that this is the routing table for.
*
* @return The protocol name.
*/
public String getProtocol() {
return protocol;
}
/**
* Returns whether or not there are any hop specs contained in this.
*
* @return True if there is at least one hop.
*/
public boolean hasHops() {
return !hops.isEmpty();
}
/**
* Returns whether or not there is a named hop spec contained in this.
*
* @param hopName The hop name to check for.
* @return True if the hop exists.
*/
public boolean hasHop(String hopName) {
for (HopSpec hop : hops) {
if (hop.getName().equals(hopName)) {
return true;
}
}
return false;
}
/**
* Returns the number of hops that are contained in this table.
*
* @return The number of hops.
*/
public int getNumHops() {
return hops.size();
}
/**
* Returns the hop spec at the given index.
*
* @param i The index of the hop to return.
* @return The hop at the given position.
*/
public HopSpec getHop(int i) {
return hops.get(i);
}
/**
* Adds the given hop spec to this.
*
* @param hop The hop to add.
* @return This, to allow chaining.
*/
public RoutingTableSpec addHop(HopSpec hop) {
hops.add(hop);
return this;
}
/**
* Sets the hop spec at the given index.
*
* @param i The index at which to set the hop.
* @param hop The hop to set.
* @return This, to allow chaining.
*/
public RoutingTableSpec setHop(int i, HopSpec hop) {
hops.set(i, hop);
return this;
}
/**
* Removes the hop spec at the given index.
*
* @param i The index of the hop to remove.
* @return The removed hop.
*/
public HopSpec removeHop(int i) {
return hops.remove(i);
}
/**
* Clears the list of hop specs contained in this.
*
* @return This, to allow chaining.
*/
public RoutingTableSpec clearHops() {
hops.clear();
return this;
}
/**
* Returns whether or not there are any route specs contained in this.
*
* @return True if there is at least one route.
*/
public boolean hasRoutes() {
return !routes.isEmpty();
}
/**
* Returns whether or not there is a named route spec contained in this.
*
* @param routeName The hop name to check for.
* @return True if the hop exists.
*/
public boolean hasRoute(String routeName) {
for (RouteSpec route : routes) {
if (route.getName().equals(routeName)) {
return true;
}
}
return false;
}
/**
* Returns the number of route specs contained in this.
*
* @return The number of routes.
*/
public int getNumRoutes() {
return routes.size();
}
/**
* Returns the route spec at the given index.
*
* @param i The index of the route to return.
* @return The route at the given index.
*/
public RouteSpec getRoute(int i) {
return routes.get(i);
}
/**
* Adds a route spec to this.
*
* @param route The route to add.
* @return This, to allow chaining.
*/
public RoutingTableSpec addRoute(RouteSpec route) {
routes.add(route);
return this;
}
/**
* Sets the route spec at the given index.
*
* @param i The index at which to set the route.
* @param route The route to set.
* @return This, to allow chaining.
*/
public RoutingTableSpec setRoute(int i, RouteSpec route) {
routes.set(i, route);
return this;
}
/**
* Removes a route spec at a given index.
*
* @param i The index of the route to remove.
* @return The removed route.
*/
public RouteSpec removeRoute(int i) {
return routes.remove(i);
}
/**
* Clears the list of routes that are contained in this.
*
* @return This, to allow chaining.
*/
public RoutingTableSpec clearRoutes() {
routes.clear();
return this;
}
/**
* A convenience function to add a new hop to this routing table.
*
* @param name A protocol-unique name for this hop.
* @param selector A string that represents the selector for this hop.
* @param recipients A list of recipients for this hop.
* @return This, to allow chaining.
*/
public RoutingTableSpec addHop(String name, String selector, List recipients) {
return addHop(new HopSpec(name, selector).addRecipients(recipients));
}
/**
* A convenience function to add a new route to this routing table.
*
* @param name A protocol-unique name for this route.
* @param hops A list of hops for this route.
* @return This, to allow chaining.
*/
public RoutingTableSpec addRoute(String name, List hops) {
return addRoute(new RouteSpec(name).addHops(hops));
}
/**
* 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) {
// Verify and count hops.
Map hopNames = new HashMap();
for (HopSpec hop : hops) {
String name = hop.getName();
int count = hopNames.containsKey(name) ? hopNames.get(name) : 0;
hopNames.put(name, count + 1);
hop.verify(app, this, errors);
}
for (Map.Entry entry : hopNames.entrySet()) {
int count = entry.getValue();
if (count > 1) {
errors.add("Hop '" + entry.getKey() + "' in routing table '" + protocol + "' is defined " +
count + " times.");
}
}
// Verify and count routes.
Map routeNames = new HashMap();
for (RouteSpec route : routes) {
String name = route.getName();
int count = routeNames.containsKey(name) ? routeNames.get(name) : 0;
routeNames.put(name, count + 1);
route.verify(app, this, errors);
}
for (Map.Entry entry : routeNames.entrySet()) {
int count = entry.getValue();
if (count > 1) {
errors.add("Route '" + entry.getKey() + "' in routing table '" + protocol + "' is defined " +
count + " times.");
}
}
}
return errors.isEmpty();
}
/**
* Sorts the hops and routes of this table by name. This is useful for generating a stable config for testing.
*/
public void sort() {
Collections.sort(hops, new Comparator() {
public int compare(HopSpec lhs, HopSpec rhs) {
return lhs.getName().compareTo(rhs.getName());
}
});
Collections.sort(routes, new Comparator() {
public int compare(RouteSpec lhs, RouteSpec rhs) {
return lhs.getName().compareTo(rhs.getName());
}
});
}
/**
* 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) {
cfg.append(prefix).append("protocol ").append(RoutingSpec.toConfigString(protocol)).append("\n");
int numHops = hops.size();
if (numHops > 0) {
cfg.append(prefix).append("hop[").append(numHops).append("]\n");
for (int i = 0; i < numHops; ++i) {
hops.get(i).toConfig(cfg, prefix + "hop[" + i + "].");
}
}
int numRoutes = routes.size();
if (numRoutes > 0) {
cfg.append(prefix).append("route[").append(numRoutes).append("]\n");
for (int i = 0; i < numRoutes; ++i) {
routes.get(i).toConfig(cfg, prefix + "route[" + i + "].");
}
}
}
// Overrides Object.
@Override
public String toString() {
StringBuilder ret = new StringBuilder();
toConfig(ret, "");
return ret.toString();
}
// Overrides Object.
@Override
public boolean equals(Object obj) {
if (!(obj instanceof RoutingTableSpec)) {
return false;
}
RoutingTableSpec rhs = (RoutingTableSpec)obj;
if (!protocol.equals(rhs.protocol)) {
return false;
}
if (!hops.equals(rhs.hops)) {
return false;
}
if (!routes.equals(rhs.routes)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = protocol != null ? protocol.hashCode() : 0;
result = 31 * result + (hops != null ? hops.hashCode() : 0);
result = 31 * result + (routes != null ? routes.hashCode() : 0);
result = 31 * result + (verify ? 1 : 0);
return result;
}
}