/******************************************************************************* * 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", ""); } }