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