--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.project.management;\r
+\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.net.BindException;\r
+import java.net.Socket;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Properties;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.simantics.databoard.util.StreamUtil;\r
+import org.simantics.db.Driver;\r
+import org.simantics.db.Driver.Management;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.ServerEx;\r
+import org.simantics.db.ServerI;\r
+import org.simantics.db.ServiceLocator;\r
+import org.simantics.db.Session;\r
+import org.simantics.db.WriteOnlyGraph;\r
+import org.simantics.db.common.request.ReadRequest;\r
+import org.simantics.db.common.request.WriteOnlyRequest;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.service.ClusterUID;\r
+import org.simantics.db.service.XSupport;\r
+import org.simantics.graph.db.CoreInitialization;\r
+import org.simantics.layer0.DatabaseManagementResource;\r
+import org.simantics.layer0.Layer0;\r
+\r
+/**\r
+ * Server Manager handles starting and pooling of ProCore server instances.\r
+ *\r
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
+ */\r
+public class ServerManager {\r
+\r
+ /** Default properties with default user and password */\r
+ public static final Properties DEFAULT;\r
+\r
+ /** Driver for database." */\r
+ final Driver driver;\r
+\r
+ /** Actual Server Instances. This object is synchronized by itself as lock. */\r
+ Map<File, ServerHost> servers = Collections.synchronizedMap( new HashMap<File, ServerHost>() );\r
+\r
+ /**\r
+ * Create a new server manager.\r
+ *\r
+ * @param applicationDirectory location of org.simantics.db.build\r
+ * @throws IOException\r
+ */\r
+ public ServerManager(Driver driver) throws IOException {\r
+ this.driver = driver;\r
+ }\r
+ public Management getManagement(File dbFolder) throws DatabaseException {\r
+ // We are using ProCoreDriver and know it's address format and security model. Not good!\r
+ return driver.getManagement(dbFolder.getAbsolutePath(), null);\r
+ }\r
+ /**\r
+ * Create a new database that is initialized with given graphs.\r
+ * One of them must be layer0.\r
+ * Database directory is created if it did not exist.\r
+ *\r
+ * @param databaseDirectory place where database is installed\r
+ * @param initialGraphs initialGraphs to install\r
+ * @throws DatabaseException\r
+ */\r
+ public Session createDatabase(File databaseDirectory) throws DatabaseException {\r
+ try {\r
+ Logger myLogger = Logger.getLogger(ServerManager.class);\r
+ myLogger.debug("Creating database to "+ databaseDirectory);\r
+\r
+ Session session = null;\r
+ ServerEx server1 = getServer(databaseDirectory);\r
+ server1.start();\r
+ try {\r
+ // This will initialize the fixed URIs and corresponding resources.\r
+ // These are needed by the query system to parse URIs.\r
+ // The server will generate the clusters for the generated resources.\r
+ // The layer0 statements will be generated in phase two.\r
+ // This will close the connection to server because the only thing\r
+ // you can do with this connection is to initialize the fixed URIs.\r
+ Properties info = new Properties();\r
+ info.setProperty("user", "Default User");\r
+ info.setProperty("password", "");\r
+ session = server1.createSession(info);\r
+ XSupport xs = session.getService(XSupport.class);\r
+ ClusterUID[] clusters = xs.listClusters();\r
+ if (clusters.length > 1) {// Database contain clusters, assuming initialization is done.");\r
+ ReadRequest req = new ReadRequest() {\r
+ @Override\r
+ public void run(ReadGraph g) {\r
+ // Registers Layer0 with the session ServiceLocator.\r
+ Layer0.getInstance(g);\r
+ }\r
+ };\r
+ session.syncRequest(req);\r
+ return session;\r
+ }\r
+ CoreInitialization.initializeBuiltins(session);\r
+ // This will try to initialize Builtins.class but because there is no statements\r
+ // in the server only the previously added fixed URIs are found.\r
+ // If we'd want to get rid of the missing layer0 URI warnings then\r
+ // a non initialized session should be used to add graph statements\r
+ // without using Builtins.class at all or by initializing Builtins.class\r
+ // only with the fixed URIs.\r
+ session.getService(XSupport.class).setServiceMode(true, true);\r
+\r
+ // This will add layer0 statements. The query mechanism is not\r
+ // yet totally functional because there is no statements in the\r
+ // server. Mainly WriteOnly request is available here.\r
+ GraphBundle l0 = PlatformUtil.getGraph("org.simantics.layer0");\r
+ final GraphBundleEx l0ex = GraphBundleEx.extend(l0);\r
+ l0ex.build();\r
+ long[] resourceArray = CoreInitialization.initializeGraph(session, l0ex.getGraph());\r
+ l0ex.setResourceArray(resourceArray);\r
+ session.getService(XSupport.class).setServiceMode(true, true);\r
+\r
+ DatabaseManagementResource.getInstance(session);\r
+ Layer0.getInstance(session);\r
+ session.syncRequest(new WriteOnlyRequest() {\r
+ @Override\r
+ public void perform(WriteOnlyGraph graph) throws DatabaseException {\r
+ // Root Library is a cluster set\r
+ graph.newClusterSet(graph.getRootLibrary());\r
+ DatabaseManagement mgt = new DatabaseManagement();\r
+ mgt.createGraphBundle(graph, l0ex);\r
+ graph.flushCluster();\r
+ }});\r
+ return session;\r
+ } finally {\r
+ if (null == session)\r
+ server1.stop();\r
+ }\r
+ } catch (Exception e) {\r
+ throw new DatabaseException("Failed to create Simantics database.", e);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Get a server that can be started and stopped.\r
+ *\r
+ * The result is actually a proxy server. Each successful start() increases\r
+ * reference count and stop() decreases. The actual server is closed\r
+ * once all proxies are closed.\r
+ *\r
+ * @param databaseDirectory\r
+ * @return server\r
+ * @throws DatabaseException\r
+ */\r
+ private ServerEx getServer(File databaseDirectory) throws DatabaseException {\r
+ File file = databaseDirectory.getAbsoluteFile();\r
+\r
+ ServerHost host = null;\r
+ synchronized(servers) {\r
+ host = servers.get(file);\r
+ if (host==null) {\r
+ // Instantiate actual server. We are using ProCoreDriver and know it's address format and security model. Not good!\r
+ ServerI server = driver.getServer(file.getAbsolutePath(), null);\r
+\r
+ try {\r
+ host = new ServerHost(server, databaseDirectory);\r
+ } catch (IOException e) {\r
+ throw new DatabaseException("Failed to load " + databaseDirectory, e);\r
+ }\r
+\r
+ servers.put(file, host);\r
+ }\r
+ }\r
+\r
+ ServerEx proxy = new ProxyServer(host);\r
+ return proxy;\r
+ }\r
+\r
+ /**\r
+ * @param parseUnresolved\r
+ * @return\r
+ */\r
+// public ServerEx getServer(ServerAddress endpoint) {\r
+// return new ConnectedServer(endpoint);\r
+// }\r
+\r
+ /**\r
+ * Close the server manager, close all servers.\r
+ * Deletes temporary files.\r
+ */\r
+ public void close() {\r
+ synchronized(servers) {\r
+ for (ServerHost host : servers.values()) {\r
+ ServerI server = host.actual;\r
+ try {\r
+ if (server.isActive())\r
+ server.stop();\r
+ } catch (DatabaseException e) {\r
+ Logger myLogger = Logger.getLogger(ServerManager.class);\r
+ myLogger.error(e);\r
+ }\r
+ }\r
+ servers.clear();\r
+ }\r
+ }\r
+\r
+ public static int getFreeEphemeralPort() {\r
+ while(true) {\r
+ try {\r
+ Socket s = null;\r
+ try {\r
+ s = new Socket();\r
+ s.bind(null);\r
+ return s.getLocalPort();\r
+ } finally {\r
+ if (s != null)\r
+ s.close();\r
+ }\r
+ } catch(BindException e) {\r
+ // Nothing to do, try next port\r
+ } catch (Throwable e) {\r
+ throw new Error(e);\r
+ }\r
+ }\r
+ }\r
+\r
+ public static void createServerConfig(File file) throws IOException {\r
+ InputStream is = ServerManager.class.getResourceAsStream("server_template.cnfg");\r
+ byte[] data = StreamUtil.readFully(is);\r
+ is.close();\r
+\r
+ FileOutputStream os = new FileOutputStream(file, false);\r
+ os.write(data);\r
+ Properties properties = new Properties();\r
+ properties.store(os, "# automatically generated properties");\r
+ os.close();\r
+ }\r
+\r
+ /**\r
+ * ServerHost hosts a ServerI instance. For each successful start() a\r
+ * reference count is increased and each stop() & kill() it is decreased.\r
+ */\r
+ class ServerHost implements ServerEx {\r
+\r
+ File database;\r
+ ServerI actual;\r
+ int refCount = 0;\r
+ Properties properties;\r
+\r
+ public ServerHost(ServerI actual, File database)\r
+ throws IOException {\r
+ this.actual = actual;\r
+ this.database = database;\r
+ this.properties = new Properties();\r
+ }\r
+\r
+ public File getDatabase() {\r
+ return database;\r
+ }\r
+\r
+ /**\r
+ * Get properties\r
+ * @return properties\r
+ */\r
+ public Properties getProperties() {\r
+ return properties;\r
+ }\r
+\r
+ @Override\r
+ public String getAddress()\r
+ throws DatabaseException {\r
+ return actual.getAddress();\r
+ }\r
+\r
+// @Override\r
+// public synchronized ServerAddress getServerAddress()\r
+// throws DatabaseException {\r
+// throw new DatabaseException("ServerHost.getServerAddress is not supported. Use getAddress instead.");\r
+// }\r
+\r
+ @Override\r
+ public boolean isActive() {\r
+ try {\r
+ return actual.isActive();\r
+ } catch (DatabaseException e) {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Start server if refCount = 0. If running or start was successful\r
+ * the refcount is increased.\r
+ *\r
+ * For each succesful start(), a stop() or kill() is expected.\r
+ */\r
+ @Override\r
+ public void start() throws DatabaseException {\r
+ boolean isRunning = actual.isActive();\r
+\r
+ if (!isRunning) {\r
+ actual.start();\r
+ }\r
+\r
+ refCount++;\r
+ }\r
+\r
+ @Override\r
+ public void stop() throws DatabaseException {\r
+ if (refCount <= 0)\r
+ throw new DatabaseException("Trying to stop a standing process.");\r
+ refCount--;\r
+ if (refCount > 1)\r
+ return;\r
+ actual.stop();\r
+ }\r
+\r
+ @Override\r
+ public Session createSession(Properties properties) throws DatabaseException {\r
+ return driver.getSession(actual.getAddress(), properties);\r
+ }\r
+\r
+ @Override\r
+ public ServiceLocator getServiceLocator(Properties info) throws DatabaseException {\r
+ return createSession(info);\r
+ }\r
+\r
+ @Override\r
+ public String execute(String command) throws DatabaseException {\r
+ return actual.execute(command);\r
+ }\r
+\r
+ @Override\r
+ public String executeAndDisconnect(String command) throws DatabaseException {\r
+ return actual.executeAndDisconnect(command);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Proxy Server starts actual server (ServerHost) when first start():ed,\r
+ * and closes the actual server once all proxy servers are closed.\r
+ *\r
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
+ */\r
+ public class ProxyServer implements ServerEx {\r
+\r
+ boolean running;\r
+ ServerHost actual;\r
+\r
+ public ProxyServer(ServerHost actual) {\r
+ this.actual = actual;\r
+ }\r
+\r
+ public File getDatabase() {\r
+ return actual.getDatabase();\r
+ }\r
+\r
+ /**\r
+ * Get server properties\r
+ *\r
+ * @return properties\r
+ * @throws IOException\r
+ */\r
+ public Properties getProperties() {\r
+ return actual.getProperties();\r
+ }\r
+\r
+ @Override\r
+ public String getAddress()\r
+ throws DatabaseException {\r
+ return actual.getAddress();\r
+ }\r
+\r
+// @Override\r
+// public synchronized ServerAddress getServerAddress()\r
+// throws DatabaseException {\r
+// return actual.getServerAddress();\r
+// }\r
+\r
+ @Override\r
+ public boolean isActive() {\r
+ return running && actual.isActive();\r
+ }\r
+\r
+ @Override\r
+ public void start() throws DatabaseException {\r
+ if (running) return;\r
+ actual.start();\r
+ running = true;\r
+ }\r
+\r
+ @Override\r
+ public void stop() throws DatabaseException {\r
+ if (!running) return;\r
+ actual.stop();\r
+ running = false;\r
+ }\r
+\r
+ @Override\r
+ public Session createSession(Properties properties) throws DatabaseException {\r
+ return driver.getSession(actual.getAddress(), properties);\r
+ }\r
+\r
+ @Override\r
+ public ServiceLocator getServiceLocator(Properties info) throws DatabaseException {\r
+ return createSession(info);\r
+ }\r
+\r
+ @Override\r
+ public String execute(String command) throws DatabaseException {\r
+ return actual.execute(command);\r
+ }\r
+\r
+ @Override\r
+ public String executeAndDisconnect(String command) throws DatabaseException {\r
+ return actual.executeAndDisconnect(command);\r
+ }\r
+ }\r
+\r
+// public class ConnectedServer implements ServerEx {\r
+//\r
+// ServerAddress endpoint;\r
+//\r
+// public ConnectedServer(ServerAddress endpoint) {\r
+// this.endpoint = endpoint;\r
+// }\r
+//\r
+// @Override\r
+// public void start() throws DatabaseException {\r
+// // Intentional NOP. Cannot control through socket.\r
+// }\r
+//\r
+// @Override\r
+// public void stop() throws DatabaseException {\r
+// // Intentional NOP. Cannot control through socket.\r
+// }\r
+//\r
+// @Override\r
+// public boolean isActive() {\r
+// // Without better knowledge\r
+// return true;\r
+// }\r
+//\r
+// @Override\r
+// public String getAddress()\r
+// throws DatabaseException {\r
+// return endpoint.getDbid();\r
+// }\r
+//\r
+// @Override\r
+// public synchronized ServerAddress getServerAddress()\r
+// throws DatabaseException {\r
+// return new ServerAddress(endpoint.getAddress());\r
+// }\r
+//\r
+// @Override\r
+// public Session createSession(Properties properties)\r
+// throws DatabaseException {\r
+// return driver.getSession(getServerAddress().toString(), properties);\r
+// }\r
+//\r
+// @Override\r
+// public ServiceLocator getServiceLocator(Properties info) throws DatabaseException {\r
+// return createSession(info);\r
+// }\r
+//\r
+// @Override\r
+// public String execute(String command) throws DatabaseException {\r
+// // Intentional NOP. Cannot control through socket.\r
+// return null;\r
+// }\r
+//\r
+// @Override\r
+// public String executeAndDisconnect(String command) throws DatabaseException {\r
+// // Intentional NOP. Cannot control through socket.\r
+// return null;\r
+// }\r
+// }\r
+\r
+ static {\r
+ DEFAULT = new Properties();\r
+ DEFAULT.setProperty("user", "Default User");\r
+ DEFAULT.setProperty("password", "");\r
+ }\r
+\r
+}\r
+\r