Mercurial > hg > monetdb-java
comparison src/main/java/nl/cwi/monetdb/merovingian/Control.java @ 0:a5a898f6886c
Copy of MonetDB java directory changeset e6e32756ad31.
author | Sjoerd Mullender <sjoerd@acm.org> |
---|---|
date | Wed, 21 Sep 2016 09:34:48 +0200 (2016-09-21) |
parents | |
children | 57978db4ee57 b9b35ca2eec2 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:a5a898f6886c |
---|---|
1 /* | |
2 * This Source Code Form is subject to the terms of the Mozilla Public | |
3 * License, v. 2.0. If a copy of the MPL was not distributed with this | |
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
5 * | |
6 * Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V. | |
7 */ | |
8 | |
9 package nl.cwi.monetdb.merovingian; | |
10 | |
11 import nl.cwi.monetdb.mcl.net.MapiSocket; | |
12 import nl.cwi.monetdb.mcl.io.*; | |
13 import nl.cwi.monetdb.mcl.MCLException; | |
14 import nl.cwi.monetdb.mcl.parser.MCLParseException; | |
15 import java.io.BufferedReader; | |
16 import java.io.IOException; | |
17 import java.io.InputStreamReader; | |
18 import java.io.PrintStream; | |
19 import java.net.Socket; | |
20 import java.net.URI; | |
21 import java.net.URISyntaxException; | |
22 import java.util.ArrayList; | |
23 import java.util.Collections; | |
24 import java.util.List; | |
25 import java.util.Properties; | |
26 | |
27 /** | |
28 * A Control class to perform operations on a remote merovingian | |
29 * instance, using the TCP control protocol. | |
30 * | |
31 * This class implements the protocol specific bits to perform all | |
32 * possible actions against a merovingian server that has remote control | |
33 * facilities enabled. | |
34 * <br /> | |
35 * In the merovingian world, other merovingians in the vicinity are | |
36 * known to each merovingian, allowing to perform cluster wide actions. | |
37 * The implementation taken in this class is to require one known | |
38 * merovingian to get insight in the entire network. Note that | |
39 * connecting to a merovingian requires a passphrase that is likely to | |
40 * be different for each merovingian. | |
41 * | |
42 * @author Fabian Groffen | |
43 * @version 1.0 | |
44 */ | |
45 public class Control { | |
46 /** The host to connect to */ | |
47 private final String host; | |
48 /** The port to connect to */ | |
49 private final int port; | |
50 /** The passphrase to use when connecting */ | |
51 private final String passphrase; | |
52 /** The file we should write MapiSocket debuglog to */ | |
53 private String debug; | |
54 | |
55 | |
56 /** | |
57 * Constructs a new Control object. | |
58 * | |
59 * @throws IllegalArgumentException if host, port or passphrase are | |
60 * null or <= 0 | |
61 */ | |
62 public Control(String host, int port, String passphrase) | |
63 throws IllegalArgumentException | |
64 { | |
65 this.host = host; | |
66 this.port = port; | |
67 this.passphrase = passphrase; | |
68 } | |
69 | |
70 /** | |
71 * Instructs to write a MCL protocol debug log to the given file. | |
72 * This affects any newly performed command, and can be changed | |
73 * inbetween commands. Passing null to this method disables the | |
74 * debug log. | |
75 * | |
76 * @param filename the filename to write debug information to, or null | |
77 */ | |
78 public void setDebug(String filename) { | |
79 this.debug = filename; | |
80 } | |
81 | |
82 private String controlHash(String pass, String salt) { | |
83 long ho; | |
84 long h = 0; | |
85 | |
86 /* use a very simple hash function designed for a single int val | |
87 * (hash buckets), we can make this more interesting if necessary in | |
88 * the future. | |
89 * http://www.cs.hmc.edu/~geoff/classes/hmc.cs070.200101/homework10/hashfuncs.html */ | |
90 | |
91 for (int i = 0; i < pass.length(); i++) { | |
92 ho = h & 0xF8000000L; | |
93 h <<= 5; | |
94 h &= 0xFFFFFFFFL; | |
95 h ^= ho >>> 27; | |
96 h ^= (int)(pass.charAt(i)); | |
97 } | |
98 | |
99 for (int i = 0; i < salt.length(); i++) { | |
100 ho = h & 0xF8000000L; | |
101 h <<= 5; | |
102 h &= 0xFFFFFFFFL; | |
103 h ^= ho >> 27; | |
104 h ^= (int)(salt.charAt(i)); | |
105 } | |
106 | |
107 return(Long.toString(h)); | |
108 } | |
109 | |
110 final static private String RESPONSE_OK = "OK"; | |
111 | |
112 private List<String> sendCommand( | |
113 String database, String command, boolean hasOutput) | |
114 throws MerovingianException, IOException | |
115 { | |
116 BufferedMCLReader min; | |
117 BufferedMCLWriter mout; | |
118 MapiSocket ms = new MapiSocket(); | |
119 ms.setDatabase("merovingian"); | |
120 ms.setLanguage("control"); | |
121 if (debug != null) | |
122 ms.debug(debug); | |
123 try { | |
124 ms.connect(host, port, "monetdb", passphrase); | |
125 min = ms.getReader(); | |
126 mout = ms.getWriter(); | |
127 } catch (MCLParseException e) { | |
128 throw new MerovingianException(e.getMessage()); | |
129 } catch (MCLException e) { | |
130 throw new MerovingianException(e.getMessage()); | |
131 } catch (AssertionError e) { // mcl panics | |
132 ms.close(); | |
133 | |
134 // Try old protocol instead | |
135 Socket s; | |
136 PrintStream out; | |
137 BufferedReader in; | |
138 s = new Socket(host, port); | |
139 out = new PrintStream(s.getOutputStream()); | |
140 in = new BufferedReader( | |
141 new InputStreamReader(s.getInputStream())); | |
142 try { | |
143 /* login ritual, step 1: get challenge from server */ | |
144 String response = in.readLine(); | |
145 if (response == null) | |
146 throw new MerovingianException("server closed the connection"); | |
147 | |
148 if (!response.startsWith("merovingian:1:") && | |
149 !response.startsWith("merovingian:2:")) | |
150 throw new MerovingianException("unsupported merovingian server"); | |
151 | |
152 String[] tokens = response.split(":"); | |
153 if (tokens.length < 3) | |
154 throw new MerovingianException("did not understand merovingian server"); | |
155 String version = tokens[1]; | |
156 String token = tokens[2]; | |
157 | |
158 response = controlHash(passphrase, token); | |
159 if (version.equals("1")) { | |
160 out.print(response + "\n"); | |
161 } else if (version.equals("2")) { | |
162 // we only support control mode for now | |
163 out.print(response + ":control\n"); | |
164 } | |
165 | |
166 response = in.readLine(); | |
167 if (response == null) { | |
168 throw new MerovingianException("server closed the connection"); | |
169 } | |
170 | |
171 if (!response.equals(RESPONSE_OK)) { | |
172 throw new MerovingianException(response); | |
173 } | |
174 | |
175 /* send command, form is simple: "<db> <cmd>\n" */ | |
176 out.print(database + " " + command + "\n"); | |
177 | |
178 /* Response has the first line either "OK\n" or an error | |
179 * message. In case of a command with output, the data will | |
180 * follow the first line */ | |
181 response = in.readLine(); | |
182 if (response == null) { | |
183 throw new MerovingianException("server closed the connection"); | |
184 } | |
185 if (!response.equals(RESPONSE_OK)) { | |
186 throw new MerovingianException(response); | |
187 } | |
188 | |
189 if (!hasOutput) | |
190 return null; | |
191 | |
192 ArrayList<String> l = new ArrayList<String>(); | |
193 while ((response = in.readLine()) != null) { | |
194 l.add(response); | |
195 } | |
196 return l; | |
197 } finally { | |
198 in.close(); | |
199 out.close(); | |
200 s.close(); | |
201 } | |
202 } | |
203 | |
204 mout.writeLine(database + " " + command + "\n"); | |
205 ArrayList<String> l = new ArrayList<String>(); | |
206 String tmpLine = min.readLine(); | |
207 int linetype = min.getLineType(); | |
208 if (linetype == BufferedMCLReader.ERROR) | |
209 throw new MerovingianException(tmpLine.substring(6)); | |
210 if (linetype != BufferedMCLReader.RESULT) | |
211 throw new MerovingianException("unexpected line: " + tmpLine); | |
212 if (!tmpLine.substring(1).equals(RESPONSE_OK)) | |
213 throw new MerovingianException(tmpLine.substring(1)); | |
214 tmpLine = min.readLine(); | |
215 linetype = min.getLineType(); | |
216 while (linetype != BufferedMCLReader.PROMPT) { | |
217 if (linetype != BufferedMCLReader.RESULT) | |
218 throw new MerovingianException("unexpected line: " + | |
219 tmpLine); | |
220 | |
221 l.add(tmpLine.substring(1)); | |
222 | |
223 tmpLine = min.readLine(); | |
224 linetype = min.getLineType(); | |
225 } | |
226 | |
227 ms.close(); | |
228 return l; | |
229 } | |
230 | |
231 public void start(String database) | |
232 throws MerovingianException, IOException | |
233 { | |
234 sendCommand(database, "start", false); | |
235 } | |
236 | |
237 public void stop(String database) | |
238 throws MerovingianException, IOException | |
239 { | |
240 sendCommand(database, "stop", false); | |
241 } | |
242 | |
243 public void kill(String database) | |
244 throws MerovingianException, IOException | |
245 { | |
246 sendCommand(database, "kill", false); | |
247 } | |
248 | |
249 public void create(String database) | |
250 throws MerovingianException, IOException | |
251 { | |
252 sendCommand(database, "create", false); | |
253 } | |
254 | |
255 public void destroy(String database) | |
256 throws MerovingianException, IOException | |
257 { | |
258 sendCommand(database, "destroy", false); | |
259 } | |
260 | |
261 public void lock(String database) | |
262 throws MerovingianException, IOException | |
263 { | |
264 sendCommand(database, "lock", false); | |
265 } | |
266 | |
267 public void release(String database) | |
268 throws MerovingianException, IOException | |
269 { | |
270 sendCommand(database, "release", false); | |
271 } | |
272 | |
273 public void rename(String database, String newname) | |
274 throws MerovingianException, IOException | |
275 { | |
276 if (newname == null) | |
277 newname = ""; /* force error from merovingian */ | |
278 | |
279 sendCommand(database, "name=" + newname, false); | |
280 } | |
281 | |
282 /** | |
283 * Sets property for database to value. If value is null, the | |
284 * property is unset, and its inherited value becomes active again. | |
285 * | |
286 * @param database the target database | |
287 * @param property the property to set value for | |
288 * @param value the value to set | |
289 * @throws MerovingianException if performing the command failed at | |
290 * the merovingian side | |
291 * @throws IOException if connecting to or communicating with | |
292 * merovingian failed | |
293 */ | |
294 public void setProperty(String database, String property, String value) | |
295 throws MerovingianException, IOException | |
296 { | |
297 /* inherit: set to empty string */ | |
298 if (value == null) | |
299 value = ""; | |
300 | |
301 sendCommand(database, property + "=" + value, false); | |
302 } | |
303 | |
304 public void inheritProperty(String database, String property) | |
305 throws MerovingianException, IOException | |
306 { | |
307 setProperty(database, property, null); | |
308 } | |
309 | |
310 public Properties getProperties(String database) | |
311 throws MerovingianException, IOException | |
312 { | |
313 Properties ret = new Properties(); | |
314 List<String> response = sendCommand(database, "get", true); | |
315 for (String responseLine : response) { | |
316 if (responseLine.startsWith("#")) | |
317 continue; | |
318 int pos = responseLine.indexOf("="); | |
319 if (pos > 0) { | |
320 ret.setProperty( | |
321 responseLine.substring(0, pos), | |
322 responseLine.substring(pos + 1, responseLine.length())); | |
323 } | |
324 } | |
325 return ret; | |
326 } | |
327 | |
328 public Properties getDefaultProperties() | |
329 throws MerovingianException, IOException | |
330 { | |
331 return(getProperties("#defaults")); | |
332 } | |
333 | |
334 public SabaothDB getStatus(String database) | |
335 throws MerovingianException, IOException | |
336 { | |
337 List<String> response = sendCommand(database, "status", true); | |
338 if (response.isEmpty()) | |
339 throw new MerovingianException("communication error"); | |
340 return new SabaothDB(response.get(0)); | |
341 } | |
342 | |
343 /** | |
344 * Test whether a specific database exists. | |
345 * | |
346 * @param database | |
347 * @return true, iff database already exists. | |
348 * @throws MerovingianException | |
349 * @throws IOException | |
350 */ | |
351 public boolean exists(String database) | |
352 throws MerovingianException, IOException | |
353 { | |
354 List<SabaothDB> all = getAllStatuses(); | |
355 for (SabaothDB db : all) { | |
356 if (db.getName().equals(database)) { | |
357 return true; | |
358 } | |
359 } | |
360 return false; | |
361 } | |
362 | |
363 public List<SabaothDB> getAllStatuses() | |
364 throws MerovingianException, IOException | |
365 { | |
366 List<SabaothDB> l = new ArrayList<SabaothDB>(); | |
367 List<String> response = sendCommand("#all", "status", true); | |
368 try { | |
369 for (String responseLine : response) { | |
370 l.add(new SabaothDB(responseLine)); | |
371 } | |
372 } catch (IllegalArgumentException e) { | |
373 throw new MerovingianException(e.getMessage()); | |
374 } | |
375 return Collections.unmodifiableList(l); | |
376 } | |
377 | |
378 public List<URI> getAllNeighbours() | |
379 throws MerovingianException, IOException | |
380 { | |
381 List<URI> l = new ArrayList<URI>(); | |
382 List<String> response = sendCommand("anelosimus", "eximius", true); | |
383 try { | |
384 for (String responseLine : response) { | |
385 // format is <db>\t<uri> | |
386 String[] parts = responseLine.split("\t", 2); | |
387 if (parts.length != 2) | |
388 throw new MerovingianException("invalid entry: " + | |
389 responseLine); | |
390 if (parts[0].equals("*")) { | |
391 l.add(new URI(parts[1])); | |
392 } else { | |
393 l.add(new URI(parts[1] + parts[0])); | |
394 } | |
395 } | |
396 } catch (URISyntaxException e) { | |
397 throw new MerovingianException(e.getMessage()); | |
398 } | |
399 return Collections.unmodifiableList(l); | |
400 } | |
401 } |