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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.project.management;
\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
25 import org.simantics.databoard.util.StreamUtil;
\r
26 import org.simantics.db.Driver;
\r
27 import org.simantics.db.Driver.Management;
\r
28 import org.simantics.db.ReadGraph;
\r
29 import org.simantics.db.ServerEx;
\r
30 import org.simantics.db.ServerI;
\r
31 import org.simantics.db.ServiceLocator;
\r
32 import org.simantics.db.Session;
\r
33 import org.simantics.db.WriteOnlyGraph;
\r
34 import org.simantics.db.common.request.ReadRequest;
\r
35 import org.simantics.db.common.request.WriteOnlyRequest;
\r
36 import org.simantics.db.exception.DatabaseException;
\r
37 import org.simantics.db.service.ClusterUID;
\r
38 import org.simantics.db.service.XSupport;
\r
39 import org.simantics.graph.db.CoreInitialization;
\r
40 import org.simantics.layer0.DatabaseManagementResource;
\r
41 import org.simantics.layer0.Layer0;
\r
42 import org.slf4j.Logger;
\r
43 import org.slf4j.LoggerFactory;
\r
46 * Server Manager handles starting and pooling of ProCore server instances.
\r
48 * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
\r
50 public class ServerManager {
\r
51 private static final Logger LOGGER = LoggerFactory.getLogger(ServerManager.class);
\r
53 /** Default properties with default user and password */
\r
54 public static final Properties DEFAULT;
\r
56 /** Driver for database." */
\r
57 final Driver driver;
\r
59 /** Actual Server Instances. This object is synchronized by itself as lock. */
\r
60 Map<File, ServerHost> servers = Collections.synchronizedMap( new HashMap<File, ServerHost>() );
\r
63 * Create a new server manager.
\r
65 * @param applicationDirectory location of org.simantics.db.build
\r
66 * @throws IOException
\r
68 public ServerManager(Driver driver) throws IOException {
\r
69 this.driver = driver;
\r
71 public Management getManagement(File dbFolder) throws DatabaseException {
\r
72 // We are using ProCoreDriver and know it's address format and security model. Not good!
\r
73 return driver.getManagement(dbFolder.getAbsolutePath(), null);
\r
76 * Create a new database that is initialized with given graphs.
\r
77 * One of them must be layer0.
\r
78 * Database directory is created if it did not exist.
\r
80 * @param databaseDirectory place where database is installed
\r
81 * @param initialGraphs initialGraphs to install
\r
82 * @throws DatabaseException
\r
84 public Session createDatabase(File databaseDirectory) throws DatabaseException {
\r
86 LOGGER.debug("Creating database to "+ databaseDirectory);
\r
88 Session session = null;
\r
89 ServerEx server1 = getServer(databaseDirectory);
\r
92 // This will initialize the fixed URIs and corresponding resources.
\r
93 // These are needed by the query system to parse URIs.
\r
94 // The server will generate the clusters for the generated resources.
\r
95 // The layer0 statements will be generated in phase two.
\r
96 // This will close the connection to server because the only thing
\r
97 // you can do with this connection is to initialize the fixed URIs.
\r
98 Properties info = new Properties();
\r
99 info.setProperty("user", "Default User");
\r
100 info.setProperty("password", "");
\r
101 session = server1.createSession(info);
\r
102 XSupport xs = session.getService(XSupport.class);
\r
103 ClusterUID[] clusters = xs.listClusters();
\r
104 if (clusters.length > 1) {// Database contain clusters, assuming initialization is done.");
\r
105 ReadRequest req = new ReadRequest() {
\r
107 public void run(ReadGraph g) {
\r
108 // Registers Layer0 with the session ServiceLocator.
\r
109 Layer0.getInstance(g);
\r
112 session.syncRequest(req);
\r
115 CoreInitialization.initializeBuiltins(session);
\r
116 // This will try to initialize Builtins.class but because there is no statements
\r
117 // in the server only the previously added fixed URIs are found.
\r
118 // If we'd want to get rid of the missing layer0 URI warnings then
\r
119 // a non initialized session should be used to add graph statements
\r
120 // without using Builtins.class at all or by initializing Builtins.class
\r
121 // only with the fixed URIs.
\r
122 session.getService(XSupport.class).setServiceMode(true, true);
\r
124 // This will add layer0 statements. The query mechanism is not
\r
125 // yet totally functional because there is no statements in the
\r
126 // server. Mainly WriteOnly request is available here.
\r
127 GraphBundle l0 = PlatformUtil.getGraph("org.simantics.layer0");
\r
128 final GraphBundleEx l0ex = GraphBundleEx.extend(l0);
\r
130 long[] resourceArray = CoreInitialization.initializeGraph(session, l0ex.getGraph());
\r
131 l0ex.setResourceArray(resourceArray);
\r
132 session.getService(XSupport.class).setServiceMode(true, true);
\r
134 DatabaseManagementResource.getInstance(session);
\r
135 Layer0.getInstance(session);
\r
136 session.syncRequest(new WriteOnlyRequest() {
\r
138 public void perform(WriteOnlyGraph graph) throws DatabaseException {
\r
139 // Root Library is a cluster set
\r
140 graph.newClusterSet(graph.getRootLibrary());
\r
141 DatabaseManagement mgt = new DatabaseManagement();
\r
142 mgt.createGraphBundle(graph, l0ex);
\r
143 graph.flushCluster();
\r
147 if (null == session)
\r
150 } catch (Exception e) {
\r
151 throw new DatabaseException("Failed to create Simantics database.", e);
\r
156 * Get a server that can be started and stopped.
\r
158 * The result is actually a proxy server. Each successful start() increases
\r
159 * reference count and stop() decreases. The actual server is closed
\r
160 * once all proxies are closed.
\r
162 * @param databaseDirectory
\r
164 * @throws DatabaseException
\r
166 private ServerEx getServer(File databaseDirectory) throws DatabaseException {
\r
167 File file = databaseDirectory.getAbsoluteFile();
\r
169 ServerHost host = null;
\r
170 synchronized(servers) {
\r
171 host = servers.get(file);
\r
173 // Instantiate actual server. We are using ProCoreDriver and know it's address format and security model. Not good!
\r
174 ServerI server = driver.getServer(file.getAbsolutePath(), null);
\r
177 host = new ServerHost(server, databaseDirectory);
\r
178 } catch (IOException e) {
\r
179 throw new DatabaseException("Failed to load " + databaseDirectory, e);
\r
182 servers.put(file, host);
\r
186 ServerEx proxy = new ProxyServer(host);
\r
191 * @param parseUnresolved
\r
194 // public ServerEx getServer(ServerAddress endpoint) {
\r
195 // return new ConnectedServer(endpoint);
\r
199 * Close the server manager, close all servers.
\r
200 * Deletes temporary files.
\r
202 public void close() {
\r
203 synchronized(servers) {
\r
204 for (ServerHost host : servers.values()) {
\r
205 ServerI server = host.actual;
\r
207 if (server.isActive())
\r
209 } catch (DatabaseException e) {
\r
210 LOGGER.error("Failed to stop database server.", e);
\r
217 public static int getFreeEphemeralPort() {
\r
224 return s.getLocalPort();
\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
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
242 FileOutputStream os = new FileOutputStream(file, false);
\r
244 Properties properties = new Properties();
\r
245 properties.store(os, "# automatically generated properties");
\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
253 class ServerHost implements ServerEx {
\r
258 Properties properties;
\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
267 public File getDatabase() {
\r
273 * @return properties
\r
275 public Properties getProperties() {
\r
280 public String getAddress()
\r
281 throws DatabaseException {
\r
282 return actual.getAddress();
\r
286 // public synchronized ServerAddress getServerAddress()
\r
287 // throws DatabaseException {
\r
288 // throw new DatabaseException("ServerHost.getServerAddress is not supported. Use getAddress instead.");
\r
292 public boolean isActive() {
\r
294 return actual.isActive();
\r
295 } catch (DatabaseException e) {
\r
301 * Start server if refCount = 0. If running or start was successful
\r
302 * the refcount is increased.
\r
304 * For each succesful start(), a stop() or kill() is expected.
\r
307 public void start() throws DatabaseException {
\r
308 boolean isRunning = actual.isActive();
\r
318 public void stop() throws DatabaseException {
\r
320 throw new DatabaseException("Trying to stop a standing process.");
\r
328 public Session createSession(Properties properties) throws DatabaseException {
\r
329 return driver.getSession(actual.getAddress(), properties);
\r
333 public ServiceLocator getServiceLocator(Properties info) throws DatabaseException {
\r
334 return createSession(info);
\r
338 public String execute(String command) throws DatabaseException {
\r
339 return actual.execute(command);
\r
343 public String executeAndDisconnect(String command) throws DatabaseException {
\r
344 return actual.executeAndDisconnect(command);
\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
352 * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
\r
354 public class ProxyServer implements ServerEx {
\r
359 public ProxyServer(ServerHost actual) {
\r
360 this.actual = actual;
\r
363 public File getDatabase() {
\r
364 return actual.getDatabase();
\r
368 * Get server properties
\r
370 * @return properties
\r
371 * @throws IOException
\r
373 public Properties getProperties() {
\r
374 return actual.getProperties();
\r
378 public String getAddress()
\r
379 throws DatabaseException {
\r
380 return actual.getAddress();
\r
384 // public synchronized ServerAddress getServerAddress()
\r
385 // throws DatabaseException {
\r
386 // return actual.getServerAddress();
\r
390 public boolean isActive() {
\r
391 return running && actual.isActive();
\r
395 public void start() throws DatabaseException {
\r
396 if (running) return;
\r
402 public void stop() throws DatabaseException {
\r
403 if (!running) return;
\r
409 public Session createSession(Properties properties) throws DatabaseException {
\r
410 return driver.getSession(actual.getAddress(), properties);
\r
414 public ServiceLocator getServiceLocator(Properties info) throws DatabaseException {
\r
415 return createSession(info);
\r
419 public String execute(String command) throws DatabaseException {
\r
420 return actual.execute(command);
\r
424 public String executeAndDisconnect(String command) throws DatabaseException {
\r
425 return actual.executeAndDisconnect(command);
\r
429 // public class ConnectedServer implements ServerEx {
\r
431 // ServerAddress endpoint;
\r
433 // public ConnectedServer(ServerAddress endpoint) {
\r
434 // this.endpoint = endpoint;
\r
438 // public void start() throws DatabaseException {
\r
439 // // Intentional NOP. Cannot control through socket.
\r
443 // public void stop() throws DatabaseException {
\r
444 // // Intentional NOP. Cannot control through socket.
\r
448 // public boolean isActive() {
\r
449 // // Without better knowledge
\r
454 // public String getAddress()
\r
455 // throws DatabaseException {
\r
456 // return endpoint.getDbid();
\r
460 // public synchronized ServerAddress getServerAddress()
\r
461 // throws DatabaseException {
\r
462 // return new ServerAddress(endpoint.getAddress());
\r
466 // public Session createSession(Properties properties)
\r
467 // throws DatabaseException {
\r
468 // return driver.getSession(getServerAddress().toString(), properties);
\r
472 // public ServiceLocator getServiceLocator(Properties info) throws DatabaseException {
\r
473 // return createSession(info);
\r
477 // public String execute(String command) throws DatabaseException {
\r
478 // // Intentional NOP. Cannot control through socket.
\r
483 // public String executeAndDisconnect(String command) throws DatabaseException {
\r
484 // // Intentional NOP. Cannot control through socket.
\r
490 DEFAULT = new Properties();
\r
491 DEFAULT.setProperty("user", "Default User");
\r
492 DEFAULT.setProperty("password", "");
\r