aboutsummaryrefslogtreecommitdiffstats
path: root/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingTableSpec.java
blob: 78a21dfcbd16bfa70b00d35ac91e8531417eb33a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
// 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 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.
 * <p>
 * This class contains the spec for a single routing table, which corresponds to exactly one protocol.
 *
 * @author Simon Thoresen Hult
 */
public class RoutingTableSpec {

    private final String protocol;
    private final List<HopSpec> hops = new ArrayList<>();
    private final List<RouteSpec> 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<String> 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<String> 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<String> errors) {
        if (verify) {
            // Verify and count hops.
            Map<String, Integer> hopNames = new HashMap<String, Integer>();
            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<String, Integer> 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<String, Integer> routeNames = new HashMap<String, Integer>();
            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<String, Integer> 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<HopSpec>() {
            public int compare(HopSpec lhs, HopSpec rhs) {
                return lhs.getName().compareTo(rhs.getName());
            }
        });
        Collections.sort(routes, new Comparator<RouteSpec>() {
            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;
    }
}