1 package org.simantics.jdbc.variable;
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.net.MalformedURLException;
8 import java.nio.file.Paths;
9 import java.sql.Connection;
10 import java.sql.SQLException;
11 import java.sql.Statement;
12 import java.util.Properties;
13 import java.util.function.Function;
15 import org.simantics.Simantics;
16 import org.simantics.databoard.Bindings;
17 import org.simantics.databoard.binding.error.BindingException;
18 import org.simantics.db.ReadGraph;
19 import org.simantics.db.common.request.ReadRequest;
20 import org.simantics.db.exception.DatabaseException;
21 import org.simantics.db.layer0.variable.NodeSupport;
22 import org.simantics.jdbc.SimanticsJDBC;
23 import org.simantics.simulator.toolkit.StandardRealm;
24 import org.simantics.simulator.toolkit.db.StandardSessionManager;
25 import org.simantics.simulator.variable.exceptions.NodeManagerException;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
29 import com.impossibl.postgres.api.jdbc.PGConnection;
30 import com.impossibl.postgres.api.jdbc.PGNotificationListener;
31 import com.impossibl.postgres.jdbc.PGDataSource;
33 public class JDBCSessionManager extends StandardSessionManager<JDBCNode, JDBCNodeManagerSupport> {
35 private static final Logger LOGGER = LoggerFactory.getLogger(JDBCSessionManager.class);
36 private static JDBCSessionManager INSTANCE = new JDBCSessionManager();
38 private static final String VALUE_SIMANTICS_JDBC_HOST = "localhost";
39 private static final int VALUE_SIMANTICS_JDBC_PORT = 5432;
40 private static final String VALUE_SIMANTICS_JDBC_USER = "simantics";
41 private static final String VALUE_SIMANTICS_JDBC_PASSWORD = "simantics";
42 private static final String VALUE_SIMANTICS_JDBC_DATABASE = "simantics";
44 private String channelName;
45 private PGNotificationListener listener;
47 private PGDataSource dataSource;
48 private Connection connection;
50 private static Properties readProperties(InputStream s) throws IOException {
51 try (InputStream is = s) {
52 Properties props = new Properties();
58 private static Properties safeReadProperties(URL url) {
60 return readProperties(url.openStream());
61 } catch (IOException e) {
62 LOGGER.error("Could not read props from " + url, e);
67 private static Properties safeReadProperties(URI uri) {
69 return safeReadProperties(uri.toURL());
70 } catch (MalformedURLException e) {
71 LOGGER.error("Could not read props from " + uri, e);
76 private static Properties safeReadProperties(String path) {
77 return safeReadProperties(Paths.get(path).toUri());
80 private static Properties readProperties() {
81 String propFile = System.getProperty(SimanticsJDBC.PROP_SIMANTICS_JDBC_PROPERTYFILE, null);
82 if (propFile != null) {
83 Properties p = safeReadProperties(propFile);
87 // Read default settings from built-in file and override them with values in System properties
88 Properties p = safeReadProperties(JDBCSessionManager.class.getClassLoader().getResource("jdbc.properties"));
90 p.putAll(System.getProperties());
92 p = System.getProperties();
97 public JDBCSessionManager() {
98 this.channelName = "test";
100 Properties props = readProperties();
101 String host = props.getProperty(SimanticsJDBC.PROP_SIMANTICS_JDBC_HOST, VALUE_SIMANTICS_JDBC_HOST);
102 String port = props.getProperty(SimanticsJDBC.PROP_SIMANTICS_JDBC_PORT, "" + VALUE_SIMANTICS_JDBC_PORT);
103 String database = props.getProperty(SimanticsJDBC.PROP_SIMANTICS_JDBC_DATABASE, VALUE_SIMANTICS_JDBC_DATABASE);
104 String user = props.getProperty(SimanticsJDBC.PROP_SIMANTICS_JDBC_USER, VALUE_SIMANTICS_JDBC_USER);
105 String password = props.getProperty(SimanticsJDBC.PROP_SIMANTICS_JDBC_PASSWORD, VALUE_SIMANTICS_JDBC_PASSWORD);
108 int portNumber = Integer.valueOf(port);
110 dataSource = new PGDataSource();
111 dataSource.setHost(host);
112 dataSource.setPort(portNumber);
113 dataSource.setDatabase(database);
114 dataSource.setUser(user);
115 dataSource.setPassword(password);
117 this.connection = dataSource.getConnection();
118 this.listener = new PGNotificationListener() {
120 public void notification(int processId, String channelName, String payload) {
121 if (LOGGER.isDebugEnabled())
122 LOGGER.debug("Received notification from processId={} channelName={} and payload={}", processId, channelName, payload);
123 Simantics.getSession().asyncRequest(new ReadRequest() {
125 public void run(ReadGraph graph) throws DatabaseException {
126 for (String realmId : INSTANCE.getRealms()) {
128 JDBCRealm jdbcRealm = (JDBCRealm) INSTANCE.getOrCreateRealm(graph, realmId);
129 jdbcRealm.getNodeManager().refreshVariable(new JDBCNode(payload));
130 } catch (DatabaseException e) {
131 LOGGER.error("Could not refresh variable in realm {} with payload {}", realmId, payload, e);
140 } catch (SQLException e) {
141 LOGGER.error("Could not initialize JDBCSessionManager!", e);
145 private void createTable() throws SQLException {
146 Statement statement = connection.createStatement();
147 statement.execute("CREATE TABLE IF NOT EXISTS simantics_table (key VARCHAR UNIQUE, value JSON)");
151 protected void init() throws SQLException {
152 Statement statement = connection.createStatement();
153 statement.execute("LISTEN " + this.channelName);
155 ((PGConnection) connection).addNotificationListener(this.listener);
158 protected void destroy() throws SQLException {
159 try (PGConnection connection = (PGConnection) dataSource.getConnection()) {
160 Statement statement = connection.createStatement();
161 statement.execute("UNLISTEN " + this.channelName);
168 protected JDBCNodeManagerSupport createEngine(ReadGraph graph, String id) throws DatabaseException {
169 return new JDBCNodeManagerSupport(id, this.dataSource, this.channelName);
173 protected StandardRealm<JDBCNode, JDBCNodeManagerSupport> createRealm(JDBCNodeManagerSupport engine, String id) {
174 return new JDBCRealm(engine, id);
177 public static void setValue(ReadGraph graph, String id, String key, Object value) throws DatabaseException, NodeManagerException, BindingException, InterruptedException {
178 JDBCRealm realm = (JDBCRealm) INSTANCE.getOrCreateRealm(graph, id);
179 realm.asyncExec(() -> {
181 realm.getNodeManager().setValue(new JDBCNode(key), key, value, Bindings.OBJECT);
182 } catch (NodeManagerException | BindingException e) {
183 LOGGER.error("Could not set value {} for {}", value, key, e);
188 public static NodeSupport<?> nodeSupport(ReadGraph graph, String sessionName) throws DatabaseException {
189 return INSTANCE.getOrCreateNodeSupport(graph, sessionName);
192 public static Object getValue(ReadGraph graph, String uri, String key) throws InterruptedException, DatabaseException {
193 JDBCRealm realm = (JDBCRealm) INSTANCE.getOrCreateRealm(graph, uri);
194 return realm.syncExec(new Function<Object, Object>() {
197 public Object apply(Object t) {
199 return realm.getNodeManager().getValue(new JDBCNode(key), key).getValue();
200 } catch (NodeManagerException e) {
201 LOGGER.error("Could not get value for {}", key, e);