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