]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.project/src/org/simantics/project/management/ServerManager.java
56cf3254c465300a5a8e1188d2015e9b58a37bca
[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.apache.log4j.Logger;\r
26 import org.simantics.databoard.util.StreamUtil;\r
27 import org.simantics.db.Driver;\r
28 import org.simantics.db.Driver.Management;\r
29 import org.simantics.db.ReadGraph;\r
30 import org.simantics.db.ServerEx;\r
31 import org.simantics.db.ServerI;\r
32 import org.simantics.db.ServiceLocator;\r
33 import org.simantics.db.Session;\r
34 import org.simantics.db.WriteOnlyGraph;\r
35 import org.simantics.db.common.request.ReadRequest;\r
36 import org.simantics.db.common.request.WriteOnlyRequest;\r
37 import org.simantics.db.exception.DatabaseException;\r
38 import org.simantics.db.service.ClusterUID;\r
39 import org.simantics.db.service.XSupport;\r
40 import org.simantics.graph.db.CoreInitialization;\r
41 import org.simantics.layer0.DatabaseManagementResource;\r
42 import org.simantics.layer0.Layer0;\r
43 \r
44 /**\r
45  * Server Manager handles starting and pooling of ProCore server instances.\r
46  *\r
47  * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
48  */\r
49 public class ServerManager {\r
50 \r
51         /** Default properties with default user and password */\r
52         public static final Properties DEFAULT;\r
53 \r
54         /** Driver for database." */\r
55         final Driver driver;\r
56 \r
57         /** Actual Server Instances. This object is synchronized by itself as lock. */\r
58         Map<File, ServerHost> servers = Collections.synchronizedMap( new HashMap<File, ServerHost>() );\r
59 \r
60         /**\r
61          * Create a new server manager.\r
62          *\r
63          * @param applicationDirectory location of org.simantics.db.build\r
64          * @throws IOException\r
65          */\r
66     public ServerManager(Driver driver) throws IOException {\r
67         this.driver = driver;\r
68     }\r
69     public Management getManagement(File dbFolder) throws DatabaseException {\r
70         // We are using ProCoreDriver and know it's address format and security model. Not good!\r
71         return driver.getManagement(dbFolder.getAbsolutePath(), null);\r
72     }\r
73         /**\r
74          * Create a new database that is initialized with given graphs.\r
75          * One of them must be layer0.\r
76          * Database directory is created if it did not exist.\r
77          *\r
78          * @param databaseDirectory place where database is installed\r
79          * @param initialGraphs initialGraphs to install\r
80          * @throws DatabaseException\r
81          */\r
82         public Session createDatabase(File databaseDirectory) throws DatabaseException {\r
83                 try {\r
84                         Logger myLogger = Logger.getLogger(ServerManager.class);\r
85                         myLogger.debug("Creating database to "+ databaseDirectory);\r
86 \r
87             Session session = null;\r
88             ServerEx server1 = getServer(databaseDirectory);\r
89             server1.start();\r
90                         try {\r
91                                 // This will initialize the fixed URIs and corresponding resources.\r
92                                 // These are needed by the query system to parse URIs.\r
93                                 // The server will generate the clusters for the generated resources.\r
94                                 // The layer0 statements will be generated in phase two.\r
95                                 // This will close the connection to server because the only thing\r
96                                 // you can do with this connection is to initialize the fixed URIs.\r
97                                 Properties info = new Properties();\r
98                                 info.setProperty("user", "Default User");\r
99                                 info.setProperty("password", "");\r
100                 session = server1.createSession(info);\r
101                 XSupport xs = session.getService(XSupport.class);\r
102                 ClusterUID[] clusters = xs.listClusters();\r
103                 if (clusters.length > 1) {// Database contain clusters, assuming initialization is done.");\r
104                     ReadRequest req = new ReadRequest() {\r
105                         @Override\r
106                         public void run(ReadGraph g) {\r
107                             // Registers Layer0 with the session ServiceLocator.\r
108                             Layer0.getInstance(g);\r
109                         }\r
110                     };\r
111                     session.syncRequest(req);\r
112                     return session;\r
113                 }\r
114                 CoreInitialization.initializeBuiltins(session);\r
115                                 // This will try to initialize Builtins.class but because there is no statements\r
116                                 // in the server only the previously added fixed URIs are found.\r
117                                 // If we'd want to get rid of the missing layer0 URI warnings then\r
118                                 // a non initialized session should be used to add graph statements\r
119                                 // without using Builtins.class at all or by initializing Builtins.class\r
120                                 // only with the fixed URIs.\r
121                             session.getService(XSupport.class).setServiceMode(true, true);\r
122 \r
123                                 // This will add layer0 statements. The query mechanism is not\r
124                                 // yet totally functional because there is no statements in the\r
125                                 // server. Mainly WriteOnly request is available here.\r
126                 GraphBundle l0 = PlatformUtil.getGraph("org.simantics.layer0");\r
127                 final GraphBundleEx l0ex = GraphBundleEx.extend(l0);\r
128                 l0ex.build();\r
129                                 long[] resourceArray = CoreInitialization.initializeGraph(session, l0ex.getGraph());\r
130                                 l0ex.setResourceArray(resourceArray);\r
131                                 session.getService(XSupport.class).setServiceMode(true, true);\r
132 \r
133                                 DatabaseManagementResource.getInstance(session);\r
134                 Layer0.getInstance(session);\r
135                                 session.syncRequest(new WriteOnlyRequest() {\r
136                                         @Override\r
137                                         public void perform(WriteOnlyGraph graph) throws DatabaseException {\r
138                                             // Root Library is a cluster set\r
139                                             graph.newClusterSet(graph.getRootLibrary());\r
140                                                 DatabaseManagement mgt = new DatabaseManagement();\r
141                                                 mgt.createGraphBundle(graph, l0ex);\r
142                                                 graph.flushCluster();\r
143                                         }});\r
144                     return session;\r
145                         } finally {\r
146                             if (null == session)\r
147                                 server1.stop();\r
148                         }\r
149                 } catch (Exception e) {\r
150                         throw new DatabaseException("Failed to create Simantics database.", e);\r
151                 }\r
152         }\r
153 \r
154         /**\r
155          * Get a server that can be started and stopped.\r
156          *\r
157          * The result is actually a proxy server. Each successful start() increases\r
158          * reference count and stop() decreases. The actual server is closed\r
159          * once all proxies are closed.\r
160          *\r
161          * @param databaseDirectory\r
162          * @return server\r
163          * @throws DatabaseException\r
164          */\r
165         private ServerEx getServer(File databaseDirectory) throws DatabaseException {\r
166                 File file = databaseDirectory.getAbsoluteFile();\r
167 \r
168                 ServerHost host = null;\r
169                 synchronized(servers) {\r
170                         host = servers.get(file);\r
171                         if (host==null) {\r
172                                 // Instantiate actual server. We are using ProCoreDriver and know it's address format and security model. Not good!\r
173                                 ServerI server = driver.getServer(file.getAbsolutePath(), null);\r
174 \r
175                                 try {\r
176                                         host = new ServerHost(server, databaseDirectory);\r
177                                 } catch (IOException e) {\r
178                                         throw new DatabaseException("Failed to load " + databaseDirectory, e);\r
179                                 }\r
180 \r
181                                 servers.put(file, host);\r
182                         }\r
183                 }\r
184 \r
185                 ServerEx proxy = new ProxyServer(host);\r
186                 return proxy;\r
187         }\r
188 \r
189         /**\r
190          * @param parseUnresolved\r
191          * @return\r
192          */\r
193 //      public ServerEx getServer(ServerAddress endpoint) {\r
194 //              return new ConnectedServer(endpoint);\r
195 //      }\r
196 \r
197         /**\r
198          * Close the server manager, close all servers.\r
199          * Deletes temporary files.\r
200          */\r
201         public void close() {\r
202                 synchronized(servers) {\r
203                         for (ServerHost host : servers.values()) {\r
204                                 ServerI server = host.actual;\r
205                 try {\r
206                     if (server.isActive())\r
207                         server.stop();\r
208                 } catch (DatabaseException e) {\r
209                     Logger myLogger = Logger.getLogger(ServerManager.class);\r
210                     myLogger.error(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