aboutsummaryrefslogtreecommitdiffstats
path: root/messagebus/src/main/java/com/yahoo/messagebus/routing/RoutingSpec.java
blob: 6d13d5cad365e3f6fc750fb3a9ca22113855675a (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
// 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.
 * <p>
 * This class is the root spec class for configuring message bus routing.
 *
 * @author Simon Thoresen Hult
 */
public class RoutingSpec {

    private final List<RoutingTableSpec> 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<String> errors) {
        if (verify) {
            Map<String, Integer> 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<String, Integer> 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.
     * <p>
     * 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;
    }
}