]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.project/src/org/simantics/project/management/ServerManager.java
Merge commit 'ffdf837'
[simantics/platform.git] / bundles / org.simantics.project / src / org / simantics / project / management / ServerManager.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.project.management;\r
13 \r
14 import java.io.File;\r
15 import java.io.FileOutputStream;\r
16 import java.io.IOException;\r
17 import java.io.InputStream;\r
18 import java.net.BindException;\r
19 import java.net.Socket;\r
20 import java.util.Collections;\r
21 import java.util.HashMap;\r
22 import java.util.Map;\r
23 import java.util.Properties;\r
24 \r
25 import org.simantics.databoard.util.StreamUtil;\r
26 import org.simantics.db.Driver;\r
27 import org.simantics.db.Driver.Management;\r
28 import org.simantics.db.ReadGraph;\r
29 import org.simantics.db.ServerEx;\r
30 import org.simantics.db.ServerI;\r
31 import org.simantics.db.ServiceLocator;\r
32 import org.simantics.db.Session;\r
33 import org.simantics.db.WriteOnlyGraph;\r
34 import org.simantics.db.common.request.ReadRequest;\r
35 import org.simantics.db.common.request.WriteOnlyRequest;\r
36 import org.simantics.db.exception.DatabaseException;\r
37 import org.simantics.db.service.ClusterUID;\r
38 import org.simantics.db.service.XSupport;\r
39 import org.simantics.graph.db.CoreInitialization;\r
40 import org.simantics.layer0.DatabaseManagementResource;\r
41 import org.simantics.layer0.Layer0;\r
42 import org.slf4j.Logger;\r
43 import org.slf4j.LoggerFactory;\r
44 \r
45 /**\r
46  * Server Manager handles starting and pooling of ProCore server instances.\r
47  *\r
48  * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
49  */\r
50 public class ServerManager {\r
51     private static final Logger LOGGER = LoggerFactory.getLogger(ServerManager.class);\r
52     \r
53         /** Default properties with default user and password */\r
54         public static final Properties DEFAULT;\r
55 \r
56         /** Driver for database." */\r
57         final Driver driver;\r
58 \r
59         /** Actual Server Instances. This object is synchronized by itself as lock. */\r
60         Map<File, ServerHost> servers = Collections.synchronizedMap( new HashMap<File, ServerHost>() );\r
61 \r
62         /**\r
63          * Create a new server manager.\r
64          *\r
65          * @param applicationDirectory location of org.simantics.db.build\r
66          * @throws IOException\r
67          */\r
68     public ServerManager(Driver driver) throws IOException {\r
69         this.driver = driver;\r
70     }\r
71     public Management getManagement(File dbFolder) throws DatabaseException {\r
72         // We are using ProCoreDriver and know it's address format and security model. Not good!\r
73         return driver.getManagement(dbFolder.getAbsolutePath(), null);\r
74     }\r
75         /**\r
76          * Create a new database that is initialized with given graphs.\r
77          * One of them must be layer0.\r
78          * Database directory is created if it did not exist.\r
79          *\r
80          * @param databaseDirectory place where database is installed\r
81          * @param initialGraphs initialGraphs to install\r
82          * @throws DatabaseException\r
83          */\r
84         public Session createDatabase(File databaseDirectory) throws DatabaseException {\r
85                 try {\r
86                     LOGGER.debug("Creating database to "+ databaseDirectory);\r
87 \r
88             Session session = null;\r
89             ServerEx server1 = getServer(databaseDirectory);\r
90             server1.start();\r
91                         try {\r
92                                 // This will initialize the fixed URIs and corresponding resources.\r
93                                 // These are needed by the query system to parse URIs.\r
94                                 // The server will generate the clusters for the generated resources.\r
95                                 // The layer0 statements will be generated in phase two.\r
96                                 // This will close the connection to server because the only thing\r
97                                 // you can do with this connection is to initialize the fixed URIs.\r
98                                 Properties info = new Properties();\r
99                                 info.setProperty("user", "Default User");\r
100                                 info.setProperty("password", "");\r
101                 session = server1.createSession(info);\r
102                 XSupport xs = session.getService(XSupport.class);\r
103                 ClusterUID[] clusters = xs.listClusters();\r
104                 if (clusters.length > 1) {// Database contain clusters, assuming initialization is done.");\r
105                     ReadRequest req = new ReadRequest() {\r
106                         @Override\r
107                         public void run(ReadGraph g) {\r
108                             // Registers Layer0 with the session ServiceLocator.\r
109                             Layer0.getInstance(g);\r
110                         }\r
111                     };\r
112                     session.syncRequest(req);\r
113                     return session;\r
114                 }\r
115                 CoreInitialization.initializeBuiltins(session);\r
116                                 // This will try to initialize Builtins.class but because there is no statements\r
117                                 // in the server only the previously added fixed URIs are found.\r
118                                 // If we'd want to get rid of the missing layer0 URI warnings then\r
119                                 // a non initialized session should be used to add graph statements\r
120                                 // without using Builtins.class at all or by initializing Builtins.class\r
121                                 // only with the fixed URIs.\r
122                             session.getService(XSupport.class).setServiceMode(true, true);\r
123 \r
124                                 // This will add layer0 statements. The query mechanism is not\r
125                                 // yet totally functional because there is no statements in the\r
126                                 // server. Mainly WriteOnly request is available here.\r
127                 GraphBundle l0 = PlatformUtil.getGraph("org.simantics.layer0");\r
128                 final GraphBundleEx l0ex = GraphBundleEx.extend(l0);\r
129                 l0ex.build();\r
130                                 long[] resourceArray = CoreInitialization.initializeGraph(session, l0ex.getGraph());\r
131                                 l0ex.setResourceArray(resourceArray);\r
132                                 session.getService(XSupport.class).setServiceMode(true, true);\r
133 \r
134                                 DatabaseManagementResource.getInstance(session);\r
135                 Layer0.getInstance(session);\r
136                                 session.syncRequest(new WriteOnlyRequest() {\r
137                                         @Override\r
138                                         public void perform(WriteOnlyGraph graph) throws DatabaseException {\r
139                                             // Root Library is a cluster set\r
140                                             graph.newClusterSet(graph.getRootLibrary());\r
141                                                 DatabaseManagement mgt = new DatabaseManagement();\r
142                                                 mgt.createGraphBundle(graph, l0ex);\r
143                                                 graph.flushCluster();\r
144                                         }});\r
145                     return session;\r
146                         } finally {\r
147                             if (null == session)\r
148                                 server1.stop();\r
149                         }\r
150                 } catch (Exception e) {\r
151                         throw new DatabaseException("Failed to create Simantics database.", e);\r
152                 }\r
153         }\r
154 \r
155         /**\r
156          * Get a server that can be started and stopped.\r
157          *\r
158          * The result is actually a proxy server. Each successful start() increases\r
159          * reference count and stop() decreases. The actual server is closed\r
160          * once all proxies are closed.\r
161          *\r
162          * @param databaseDirectory\r
163          * @return server\r
164          * @throws DatabaseException\r
165          */\r
166         private ServerEx getServer(File databaseDirectory) throws DatabaseException {\r
167                 File file = databaseDirectory.getAbsoluteFile();\r
168 \r
169                 ServerHost host = null;\r
170                 synchronized(servers) {\r
171                         host = servers.get(file);\r
172                         if (host==null) {\r
173                                 // Instantiate actual server. We are using ProCoreDriver and know it's address format and security model. Not good!\r
174                                 ServerI server = driver.getServer(file.getAbsolutePath(), null);\r
175 \r
176                                 try {\r
177                                         host = new ServerHost(server, databaseDirectory);\r
178                                 } catch (IOException e) {\r
179                                         throw new DatabaseException("Failed to load " + databaseDirectory, e);\r
180                                 }\r
181 \r
182                                 servers.put(file, host);\r
183                         }\r
184                 }\r
185 \r
186                 ServerEx proxy = new ProxyServer(host);\r
187                 return proxy;\r
188         }\r
189 \r
190         /**\r
191          * @param parseUnresolved\r
192          * @return\r
193          */\r
194 //      public ServerEx getServer(ServerAddress endpoint) {\r
195 //              return new ConnectedServer(endpoint);\r
196 //      }\r
197 \r
198         /**\r
199          * Close the server manager, close all servers.\r
200          * Deletes temporary files.\r
201          */\r
202         public void close() {\r
203                 synchronized(servers) {\r
204                         for (ServerHost host : servers.values()) {\r
205                                 ServerI server = host.actual;\r
206                 try {\r
207                     if (server.isActive())\r
208                         server.stop();\r
209                 } catch (DatabaseException e) {\r
210                     LOGGER.error("Failed to stop database server.", e);\r
211                 }\r
212                         }\r
213                         servers.clear();\r
214                 }\r
215         }\r
216 \r
217     public static int getFreeEphemeralPort() {\r
218         while(true) {\r
219             try {\r
220                 Socket s = null;\r
221                 try {\r
222                     s = new Socket();\r
223                     s.bind(null);\r
224                     return s.getLocalPort();\r
225                 } finally {\r
226                     if (s != null)\r
227                         s.close();\r
228                 }\r
229             } catch(BindException e) {\r
230                 // Nothing to do, try next port\r
231             } catch (Throwable e) {\r
232                 throw new Error(e);\r
233             }\r
234         }\r
235     }\r
236 \r
237     public static void createServerConfig(File file) throws IOException {\r
238                 InputStream is = ServerManager.class.getResourceAsStream("server_template.cnfg");\r
239                 byte[] data = StreamUtil.readFully(is);\r
240                 is.close();\r
241 \r
242                 FileOutputStream os = new FileOutputStream(file, false);\r
243                 os.write(data);\r
244                 Properties properties = new Properties();\r
245                 properties.store(os, "# automatically generated properties");\r
246                 os.close();\r
247         }\r
248 \r
249         /**\r
250          * ServerHost hosts a ServerI instance. For each successful start() a\r
251          * reference count is increased and each stop() & kill() it is decreased.\r
252          */\r
253         class ServerHost implements ServerEx {\r
254 \r
255                 File database;\r
256                 ServerI actual;\r
257                 int refCount = 0;\r
258                 Properties properties;\r
259 \r
260                 public ServerHost(ServerI actual, File database)\r
261                 throws IOException {\r
262                         this.actual = actual;\r
263                         this.database = database;\r
264                         this.properties = new Properties();\r
265                 }\r
266 \r
267                 public File getDatabase() {\r
268                         return database;\r
269                 }\r
270 \r
271                 /**\r
272                  * Get properties\r
273                  * @return properties\r
274                  */\r
275                 public Properties getProperties() {\r
276                         return properties;\r
277                 }\r
278 \r
279         @Override\r
280         public String getAddress()\r
281                 throws DatabaseException {\r
282             return actual.getAddress();\r
283         }\r
284 \r
285 //        @Override\r
286 //        public synchronized ServerAddress getServerAddress()\r
287 //        throws DatabaseException {\r
288 //            throw new DatabaseException("ServerHost.getServerAddress is not supported. Use getAddress instead.");\r
289 //        }\r
290 \r
291         @Override\r
292         public boolean isActive() {\r
293             try {\r
294                 return actual.isActive();\r
295             } catch (DatabaseException e) {\r
296                 return false;\r
297             }\r
298                 }\r
299 \r
300                 /**\r
301                  * Start server if refCount = 0. If running or start was successful\r
302                  * the refcount is increased.\r
303                  *\r
304                  * For each succesful start(), a stop() or kill() is expected.\r
305                  */\r
306                 @Override\r
307                 public void start() throws DatabaseException {\r
308                         boolean isRunning = actual.isActive();\r
309 \r
310                         if (!isRunning) {\r
311                                 actual.start();\r
312                         }\r
313 \r
314                         refCount++;\r
315                 }\r
316 \r
317         @Override\r
318         public void stop() throws DatabaseException {\r
319             if (refCount <= 0)\r
320                 throw new DatabaseException("Trying to stop a standing process.");\r
321             refCount--;\r
322             if (refCount > 1)\r
323                 return;\r
324             actual.stop();\r
325         }\r
326 \r
327                 @Override\r
328                 public Session createSession(Properties properties) throws DatabaseException {\r
329                         return driver.getSession(actual.getAddress(), properties);\r
330                 }\r
331 \r
332         @Override\r
333         public ServiceLocator getServiceLocator(Properties info) throws DatabaseException {\r
334             return createSession(info);\r
335         }\r
336 \r
337         @Override\r
338         public String execute(String command) throws DatabaseException {\r
339             return actual.execute(command);\r
340         }\r
341 \r
342         @Override\r
343         public String executeAndDisconnect(String command) throws DatabaseException {\r
344             return actual.executeAndDisconnect(command);\r
345         }\r
346         }\r
347 \r
348         /**\r
349          * Proxy Server starts actual server (ServerHost) when first start():ed,\r
350          * and closes the actual server once all proxy servers are closed.\r
351          *\r
352          * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
353          */\r
354         public class ProxyServer implements ServerEx {\r
355 \r
356                 boolean running;\r
357                 ServerHost actual;\r
358 \r
359                 public ProxyServer(ServerHost actual) {\r
360                         this.actual = actual;\r
361                 }\r
362 \r
363                 public File getDatabase() {\r
364                         return actual.getDatabase();\r
365                 }\r
366 \r
367                 /**\r
368                  * Get server properties\r
369                  *\r
370                  * @return properties\r
371                  * @throws IOException\r
372                  */\r
373                 public Properties getProperties() {\r
374                         return actual.getProperties();\r
375                 }\r
376 \r
377         @Override\r
378         public String getAddress()\r
379         throws DatabaseException {\r
380             return actual.getAddress();\r
381         }\r
382 \r
383 //        @Override\r
384 //        public synchronized ServerAddress getServerAddress()\r
385 //        throws DatabaseException {\r
386 //            return actual.getServerAddress();\r
387 //        }\r
388 \r
389                 @Override\r
390                 public boolean isActive() {\r
391                         return running && actual.isActive();\r
392                 }\r
393 \r
394                 @Override\r
395                 public void start() throws DatabaseException {\r
396                         if (running) return;\r
397                         actual.start();\r
398                         running = true;\r
399                 }\r
400 \r
401                 @Override\r
402                 public void stop() throws DatabaseException {\r
403                         if (!running) return;\r
404                         actual.stop();\r
405                         running = false;\r
406                 }\r
407 \r
408                 @Override\r
409                 public Session createSession(Properties properties) throws DatabaseException {\r
410                         return driver.getSession(actual.getAddress(), properties);\r
411                 }\r
412 \r
413                 @Override\r
414                 public ServiceLocator getServiceLocator(Properties info) throws DatabaseException {\r
415                         return createSession(info);\r
416                 }\r
417 \r
418         @Override\r
419         public String execute(String command) throws DatabaseException {\r
420             return actual.execute(command);\r
421         }\r
422 \r
423         @Override\r
424         public String executeAndDisconnect(String command) throws DatabaseException {\r
425             return actual.executeAndDisconnect(command);\r
426         }\r
427         }\r
428 \r
429 //      public class ConnectedServer implements ServerEx {\r
430 //\r
431 //              ServerAddress endpoint;\r
432 //\r
433 //              public ConnectedServer(ServerAddress endpoint) {\r
434 //                      this.endpoint = endpoint;\r
435 //              }\r
436 //\r
437 //              @Override\r
438 //              public void start() throws DatabaseException {\r
439 //                      // Intentional NOP. Cannot control through socket.\r
440 //              }\r
441 //\r
442 //              @Override\r
443 //              public void stop() throws DatabaseException {\r
444 //                      // Intentional NOP. Cannot control through socket.\r
445 //              }\r
446 //\r
447 //              @Override\r
448 //              public boolean isActive() {\r
449 //                      // Without better knowledge\r
450 //                      return true;\r
451 //              }\r
452 //\r
453 //        @Override\r
454 //        public String getAddress()\r
455 //        throws DatabaseException {\r
456 //            return endpoint.getDbid();\r
457 //        }\r
458 //\r
459 //        @Override\r
460 //        public synchronized ServerAddress getServerAddress()\r
461 //                throws DatabaseException {\r
462 //            return new ServerAddress(endpoint.getAddress());\r
463 //        }\r
464 //\r
465 //        @Override\r
466 //        public Session createSession(Properties properties)\r
467 //        throws DatabaseException {\r
468 //            return driver.getSession(getServerAddress().toString(), properties);\r
469 //              }\r
470 //\r
471 //        @Override\r
472 //        public ServiceLocator getServiceLocator(Properties info) throws DatabaseException {\r
473 //            return createSession(info);\r
474 //        }\r
475 //\r
476 //        @Override\r
477 //        public String execute(String command) throws DatabaseException {\r
478 //            // Intentional NOP. Cannot control through socket.\r
479 //            return null;\r
480 //        }\r
481 //\r
482 //        @Override\r
483 //        public String executeAndDisconnect(String command) throws DatabaseException {\r
484 //            // Intentional NOP. Cannot control through socket.\r
485 //            return null;\r
486 //        }\r
487 //      }\r
488 \r
489         static {\r
490                 DEFAULT = new Properties();\r
491                 DEFAULT.setProperty("user", "Default User");\r
492                 DEFAULT.setProperty("password", "");\r
493         }\r
494 \r
495 }\r
496 \r