From 0ea999d92116cb44bad9ad70351ccb7bac78cd8e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 18 Dec 2013 16:29:45 +0100 Subject: [PATCH] [New] Initial commit --- .gitignore | 4 + LICENSE | 8 +- README.md | 54 ++++- pom.xml | 86 ++++++++ .../persistence/postgresql/AbstractDao.java | 96 +++++++++ .../postgresql/DbConnectionCheck.java | 73 +++++++ .../postgresql/DbSchemaVersionCheck.java | 201 ++++++++++++++++++ .../postgresql/ModificationResult.java | 60 ++++++ .../postgresql/PostgreSqlOrderDao.java | 33 +++ .../PostgreSqlPersistenceHandler.java | 122 +++++++++++ .../postgresql/PostgreSqlResourceDao.java | 33 +++ .../PostgreSqlStrolchTransaction.java | 102 +++++++++ src/main/resources/db_schema_0.1.0_drop.sql | 5 + .../resources/db_schema_0.1.0_initial.sql | 33 +++ src/main/resources/db_version.properties | 2 + .../dao/test/AbstractDaoImplTest.java | 54 +++++ .../dao/test/ObserverUpdateTest.java | 98 +++++++++ .../dao/test/PostgreSqlOrderDaoTest.java | 92 ++++++++ .../dao/test/PostgreSqlResourceDaoTest.java | 92 ++++++++ src/test/resources/log4j.xml | 29 +++ .../runtime/config/StrolchConfiguration.xml | 27 +++ 21 files changed, 1298 insertions(+), 6 deletions(-) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/li/strolch/persistence/postgresql/AbstractDao.java create mode 100644 src/main/java/li/strolch/persistence/postgresql/DbConnectionCheck.java create mode 100644 src/main/java/li/strolch/persistence/postgresql/DbSchemaVersionCheck.java create mode 100644 src/main/java/li/strolch/persistence/postgresql/ModificationResult.java create mode 100644 src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderDao.java create mode 100644 src/main/java/li/strolch/persistence/postgresql/PostgreSqlPersistenceHandler.java create mode 100644 src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceDao.java create mode 100644 src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java create mode 100644 src/main/resources/db_schema_0.1.0_drop.sql create mode 100644 src/main/resources/db_schema_0.1.0_initial.sql create mode 100644 src/main/resources/db_version.properties create mode 100644 src/test/java/li/strolch/persistence/postgresql/dao/test/AbstractDaoImplTest.java create mode 100644 src/test/java/li/strolch/persistence/postgresql/dao/test/ObserverUpdateTest.java create mode 100644 src/test/java/li/strolch/persistence/postgresql/dao/test/PostgreSqlOrderDaoTest.java create mode 100644 src/test/java/li/strolch/persistence/postgresql/dao/test/PostgreSqlResourceDaoTest.java create mode 100644 src/test/resources/log4j.xml create mode 100644 src/test/resources/runtime/config/StrolchConfiguration.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..b284c6517 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target/ +.project +.settings +.classpath diff --git a/LICENSE b/LICENSE index e06d20818..d64569567 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ -Apache License + + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -178,7 +179,7 @@ Apache License APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" + boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +187,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -199,4 +200,3 @@ Apache License WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - diff --git a/README.md b/README.md index c5b8d2b6c..544187790 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,54 @@ li.strolch.persistence.postgresql -================================= +======================================================================= -PostgreSQL persistence implementation for Strolch +PostgreSQL Persistence Implementation for Strolch + + +Setup +======================================================================= +1. Install PostgreSQL version with at least version 9.1: + $ sudo aptitude install postgresql postgresql-client +2. Set a password for user 'postgres' + $ sudo -u postgres psql postgres + postgres=# \password postgres +3. Create the user and DB: + $ sudo su - postgres + postgres=# create user testUser with password 'test'; + postgres=# create database testdb; + postgres=# GRANT ALL PRIVILEGES ON DATABASE testdb to testuser; + postgres=# GRANT CONNECT ON DATABASE testdb TO testuser ; + +4. Added new component, setting properties for PostgreSQL DB: + + PersistenceHandler + li.strolch.persistence.api.StrolchPersistenceHandler + li.strolch.persistence.postgresql.PostgreSqlPersistenceHandler + + false + jdbc:postgresql://localhost/testdb + testUser + test + + + +5. Create tables, or allow strolch to due it for you. + + +Appendix +======================================================================= +1. To drop the user and DB: + postgres=# revoke ALL PRIVILEGES ON DATABASE testdb from testuser; + postgres=# drop user testuser; + postgres=# drop database testdb; +2. Create a database: + $ createdb -p 5432 -O drupal -U drupal -E UTF8 testingsiteone -T template0 +3. Dropping the database + $ dropdb -p 5432 -U drupal testingsiteone +4. Dumping the database + $ pg_dump -p 5432 -h localhost -Fc -U drupal --no-owner testingsiteone > /tmp/testingsiteone_$(date +"%Y-%m-%d_%s").pgdump +5. Restoring the database + $ pg_restore -p 5432 -h localhost -Fc -d testingsiteone -U drupal --no-owner < /tmp/path-to-the-file.pgdump + +References +======================================================================= +http://www.pixelite.co.nz/article/installing-and-configuring-postgresql-91-ubuntu-1204-local-drupal-development \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..70d4636c2 --- /dev/null +++ b/pom.xml @@ -0,0 +1,86 @@ + + + + li.strolch + li.strolch.parent + 0.1.0-SNAPSHOT + ../li.strolch.parent/pom.xml + + + 4.0.0 + + li.strolch.persistence.postgresql + + Reference Persistence Implementation for Strolch + Reference Persistence Implementation for Strolch + + https://github.com/eitch/li.strolch.persistence.postgresql + + 2011 + + + Github Issues + https://github.com/eitch/li.strolch.persistence.postgresql/issues + + + + scm:git:https://github.com/eitch/li.strolch.persistence.postgresql.git + scm:git:git@github.com:eitch/li.strolch.persistence.postgresql.git + https://github.com/eitch/li.strolch.persistence.postgresql + + + + + + li.strolch + li.strolch.model + + + li.strolch + li.strolch.runtime + + + li.strolch + li.strolch.persistence.api + + + + org.postgresql + postgresql + 9.3-1100-jdbc41 + + + + li.strolch + li.strolch.testbase + test + + + + + + + + org.apache.maven.plugins + maven-eclipse-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + org.apache.maven.plugins + maven-site-plugin + + + + \ No newline at end of file diff --git a/src/main/java/li/strolch/persistence/postgresql/AbstractDao.java b/src/main/java/li/strolch/persistence/postgresql/AbstractDao.java new file mode 100644 index 000000000..16b474dc8 --- /dev/null +++ b/src/main/java/li/strolch/persistence/postgresql/AbstractDao.java @@ -0,0 +1,96 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.persistence.postgresql; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import li.strolch.model.StrolchElement; +import li.strolch.persistence.api.StrolchDao; +import li.strolch.persistence.api.StrolchTransaction; + +public abstract class AbstractDao implements StrolchDao { + + protected AbstractDao(StrolchTransaction tx) { + PostgreSqlStrolchTransaction strolchTx = (PostgreSqlStrolchTransaction) tx; + } + + protected abstract String getClassType(); + + @Override + public Set queryKeySet() { + Set keys = new HashSet<>(); + Set types = queryTypes(); + for (String type : types) { + keys.addAll(queryKeySet(type)); + } + return keys; + } + + @Override + public Set queryKeySet(String type) { + return null; + } + + @Override + public Set queryTypes() { + return null; + } + + @Override + public T queryBy(String type, String id) { + return null; + } + + @Override + public List queryAll() { + return null; + } + + @Override + public List queryAll(String type) { + return null; + } + + @Override + public void save(T object) { + } + + @Override + public void saveAll(List objects) { + } + + @Override + public void update(T object) { + } + + @Override + public void updateAll(List objects) { + } + + @Override + public void remove(T object) { + } + + @Override + public void removeAll(List objects) { + } + + @Override + public void remove(String type, String id) { + } +} diff --git a/src/main/java/li/strolch/persistence/postgresql/DbConnectionCheck.java b/src/main/java/li/strolch/persistence/postgresql/DbConnectionCheck.java new file mode 100644 index 000000000..bf8263721 --- /dev/null +++ b/src/main/java/li/strolch/persistence/postgresql/DbConnectionCheck.java @@ -0,0 +1,73 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.persistence.postgresql; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import li.strolch.persistence.api.DbConnectionInfo; +import li.strolch.runtime.configuration.StrolchConfigurationException; + +/** + * @author Robert von Burg + * + */ +public class DbConnectionCheck { + + private static final Logger logger = LoggerFactory.getLogger(DbConnectionCheck.class); + private Map connetionInfoMap; + + /** + * @param connetionInfoMap + */ + public DbConnectionCheck(Map connetionInfoMap) { + this.connetionInfoMap = connetionInfoMap; + } + + public void checkConnections() { + Collection values = this.connetionInfoMap.values(); + for (DbConnectionInfo connectionInfo : values) { + String url = connectionInfo.getUrl(); + String username = connectionInfo.getUsername(); + String password = connectionInfo.getPassword(); + + try (Connection con = DriverManager.getConnection(url, username, password); + Statement st = con.createStatement();) { + + try (ResultSet rs = st.executeQuery("SELECT VERSION()")) { + if (rs.next()) { + logger.info("Connected to: " + rs.getString(1)); + } + } + + } catch (SQLException e) { + String msg = "Failed to open DB connection to URL {0} due to: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, url, e.getMessage()); + throw new StrolchConfigurationException(msg, e); + } + } + } + +} diff --git a/src/main/java/li/strolch/persistence/postgresql/DbSchemaVersionCheck.java b/src/main/java/li/strolch/persistence/postgresql/DbSchemaVersionCheck.java new file mode 100644 index 000000000..420f5d80f --- /dev/null +++ b/src/main/java/li/strolch/persistence/postgresql/DbSchemaVersionCheck.java @@ -0,0 +1,201 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.persistence.postgresql; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.Map; +import java.util.Properties; + +import li.strolch.exception.StrolchException; +import li.strolch.persistence.api.DbConnectionInfo; +import li.strolch.runtime.configuration.ComponentConfiguration; +import li.strolch.runtime.configuration.StrolchConfigurationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.dbc.DBC; +import ch.eitchnet.utils.helper.FileHelper; + +/** + * @author Robert von Burg + */ +@SuppressWarnings(value = "nls") +public class DbSchemaVersionCheck { + + private static final String RESOURCE_DB_VERSION = "/db_version.properties"; + private static final String PROP_DB_VERSION = "db_version"; + private static final String PROP_ALLOW_SCHEMA_CREATION = "allowSchemaCreation"; + private static final String PROP_ALLOW_SCHEMA_DROP = "allowSchemaDrop"; + + private static final Logger logger = LoggerFactory.getLogger(DbSchemaVersionCheck.class); + private Map connetionInfoMap; + private boolean allowSchemaCreation; + private boolean allowSchemaDrop; + + /** + * @param connetionInfoMap + * @param componentConfiguration + */ + public DbSchemaVersionCheck(Map connetionInfoMap, + ComponentConfiguration componentConfiguration) { + this.connetionInfoMap = connetionInfoMap; + + this.allowSchemaCreation = componentConfiguration.getBoolean(PROP_ALLOW_SCHEMA_CREATION, Boolean.FALSE); + this.allowSchemaDrop = componentConfiguration.getBoolean(PROP_ALLOW_SCHEMA_DROP, Boolean.FALSE); + } + + public void checkSchemaVersion() { + + Collection values = this.connetionInfoMap.values(); + + for (DbConnectionInfo connectionInfo : values) { + String realm = connectionInfo.getRealm(); + String url = connectionInfo.getUrl(); + String username = connectionInfo.getUsername(); + String password = connectionInfo.getPassword(); + + logger.info("Checking Schema version for realm " + realm + "..."); + + try (Connection con = DriverManager.getConnection(url, username, password); + Statement st = con.createStatement();) { + + String expectedDbVersion = getExpectedDbVersion(); + + // first see if we have any schema + String checkSchemaExistsSql = MessageFormat + .format("select table_schema, table_name, table_type from information_schema.tables where table_name=''{0}'';", + PROP_DB_VERSION); + try (ResultSet rs = st.executeQuery(checkSchemaExistsSql)) { + if (!rs.next()) { + createSchema(realm, expectedDbVersion, st); + } else { + checkCurrentVersion(realm, st, expectedDbVersion); + } + } + + } catch (SQLException e) { + String msg = "Failed to open DB connection to URL {0} due to: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, url, e.getMessage()); + throw new StrolchConfigurationException(msg, e); + } + } + } + + private void checkCurrentVersion(String realm, Statement st, String expectedDbVersion) throws SQLException { + try (ResultSet rs = st.executeQuery("select id, version from db_version order by id desc;")) { + if (!rs.next()) { + createSchema(realm, expectedDbVersion, st); + } else { + String currentVersion = rs.getString(2); + if (expectedDbVersion.equals(currentVersion)) { + logger.info("Schema version " + currentVersion + " is the current version. No changes needed."); + } else { + logger.warn("Schema version is not current. Need to upgrade from " + currentVersion + " to " + + expectedDbVersion); + upgradeSchema(realm, expectedDbVersion, st); + } + } + } + } + + private String getExpectedDbVersion() { + Properties dbVersionProps = new Properties(); + try (InputStream stream = getClass().getResourceAsStream(RESOURCE_DB_VERSION);) { + DBC.PRE.assertNotNull( + MessageFormat.format("Resource file with name {0} does not exist!", RESOURCE_DB_VERSION), stream); + dbVersionProps.load(stream); + } catch (IOException e) { + throw new StrolchException("Expected resource file " + RESOURCE_DB_VERSION + + " does not exist or is not a valid properties file: " + e.getMessage(), e); + } + String dbVersion = dbVersionProps.getProperty(PROP_DB_VERSION); + String msg = "Missing property {0} in resource file {1}"; + DBC.PRE.assertNotEmpty(MessageFormat.format(msg, PROP_DB_VERSION, RESOURCE_DB_VERSION), dbVersion); + return dbVersion; + } + + private String getSql(String dbVersion, String type) { + String schemaResourceS = "/db_schema_" + dbVersion + "_" + type + ".sql"; + try (InputStream stream = getClass().getResourceAsStream(schemaResourceS);) { + DBC.PRE.assertNotNull( + MessageFormat.format("Schema Resource file with name {0} does not exist!", schemaResourceS), stream); + return FileHelper.readStreamToString(stream); + } catch (IOException e) { + throw new StrolchException("Schema creation resource file is missing or could not be read: " + + schemaResourceS, e); + } + } + + /** + * @param realm + * @param st + */ + private void createSchema(String realm, String dbVersion, Statement st) { + + if (!this.allowSchemaCreation) { + throw new StrolchConfigurationException("[" + realm + + "] No schema exists, or is not valid. Schema generation is disabled, thus can not continue!"); + } + + logger.info("[" + realm + "] Creating initial schema..."); + + String sql = getSql(dbVersion, "initial"); + try { + st.execute(sql); + } catch (SQLException e) { + logger.error("Failed to execute schema creation SQL: \n" + sql); + throw new StrolchException("Failed to execute schema generation SQL: " + e.getMessage(), e); + } + + logger.info("[" + realm + "] Successfully created schema for version " + dbVersion); + } + + private void dropSchema(String realm, String dbVersion, Statement st) { + + if (!this.allowSchemaDrop) { + throw new StrolchConfigurationException("[" + realm + + "] Dropping Schema is disabled, but is required to upgrade current schema..."); + } + + logger.info("[" + realm + "] Dropping existing schema..."); + + String sql = getSql(dbVersion, "drop"); + try { + st.execute(sql); + } catch (SQLException e) { + logger.error("Failed to execute schema drop SQL: \n" + sql); + throw new StrolchException("Failed to execute schema drop SQL: " + e.getMessage(), e); + } + } + + /** + * @param st + */ + private void upgradeSchema(String realm, String dbVersion, Statement st) { + dropSchema(realm, dbVersion, st); + createSchema(realm, dbVersion, st); + } + +} diff --git a/src/main/java/li/strolch/persistence/postgresql/ModificationResult.java b/src/main/java/li/strolch/persistence/postgresql/ModificationResult.java new file mode 100644 index 000000000..21f272e54 --- /dev/null +++ b/src/main/java/li/strolch/persistence/postgresql/ModificationResult.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.persistence.postgresql; + +import java.util.ArrayList; +import java.util.List; + +public class ModificationResult { + + private final String key; + private final List created; + private final List updated; + private final List deleted; + + public ModificationResult(String key) { + this.key = key; + this.created = new ArrayList<>(); + this.updated = new ArrayList<>(); + this.deleted = new ArrayList<>(); + } + + public ModificationResult(String key, List created, List updated, List deleted) { + this.key = key; + this.created = created; + this.updated = updated; + this.deleted = deleted; + } + + public String getKey() { + return this.key; + } + + @SuppressWarnings("unchecked") + public List getCreated() { + return (List) this.created; + } + + @SuppressWarnings("unchecked") + public List getUpdated() { + return (List) this.updated; + } + + @SuppressWarnings("unchecked") + public List getDeleted() { + return (List) this.deleted; + } +} diff --git a/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderDao.java b/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderDao.java new file mode 100644 index 000000000..93177fb58 --- /dev/null +++ b/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderDao.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.persistence.postgresql; + +import li.strolch.model.Order; +import li.strolch.model.Tags; +import li.strolch.persistence.api.OrderDao; +import li.strolch.persistence.api.StrolchTransaction; + +public class PostgreSqlOrderDao extends AbstractDao implements OrderDao { + + protected PostgreSqlOrderDao(StrolchTransaction tx) { + super(tx); + } + + @Override + protected String getClassType() { + return Tags.ORDER; + } +} diff --git a/src/main/java/li/strolch/persistence/postgresql/PostgreSqlPersistenceHandler.java b/src/main/java/li/strolch/persistence/postgresql/PostgreSqlPersistenceHandler.java new file mode 100644 index 000000000..e94245522 --- /dev/null +++ b/src/main/java/li/strolch/persistence/postgresql/PostgreSqlPersistenceHandler.java @@ -0,0 +1,122 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.persistence.postgresql; + +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import li.strolch.persistence.api.DbConnectionInfo; +import li.strolch.persistence.api.OrderDao; +import li.strolch.persistence.api.ResourceDao; +import li.strolch.persistence.api.StrolchPersistenceHandler; +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.runtime.component.ComponentContainer; +import li.strolch.runtime.component.StrolchComponent; +import li.strolch.runtime.configuration.ComponentConfiguration; +import li.strolch.runtime.configuration.StrolchConfigurationException; + +/** + * @author Robert von Burg + */ +public class PostgreSqlPersistenceHandler extends StrolchComponent implements StrolchPersistenceHandler { + + private static final String PROP_DB_URL = "db.url"; + private static final String PROP_DB_USERNAME = "db.username"; + private static final String PROP_DB_PASSWORD = "db.password"; + + private ComponentConfiguration componentConfiguration; + private Map connetionInfoMap; + + public PostgreSqlPersistenceHandler(ComponentContainer container, String componentName) { + super(container, componentName); + } + + @Override + public void initialize(ComponentConfiguration componentConfiguration) { + + this.componentConfiguration = componentConfiguration; + this.connetionInfoMap = new HashMap<>(); + + String dbUrl = componentConfiguration.getString(PROP_DB_URL, null); + String username = componentConfiguration.getString(PROP_DB_USERNAME, null); + String password = componentConfiguration.getString(PROP_DB_PASSWORD, null); + + Driver driver; + try { + driver = DriverManager.getDriver(dbUrl); + } catch (SQLException e) { + String msg = "Failed to load DB driver for URL {0} due to: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, dbUrl, e.getMessage()); + throw new StrolchConfigurationException(msg, e); + } + + DbConnectionInfo connectionInfo = new DbConnectionInfo(StrolchTransaction.DEFAULT_REALM, dbUrl); + connectionInfo.setUsername(username); + connectionInfo.setPassword(password); + this.connetionInfoMap.put(StrolchTransaction.DEFAULT_REALM, connectionInfo); + + String compliant = driver.jdbcCompliant() ? "" : "non"; //$NON-NLS-1$ //$NON-NLS-2$ + String msg = "Using {0} JDBC compliant Driver {1}.{2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, compliant, driver.getMajorVersion(), driver.getMinorVersion()); + logger.info(msg); + + super.initialize(componentConfiguration); + } + + @Override + public void start() { + + // test all connections + DbConnectionCheck connectionCheck = new DbConnectionCheck(this.connetionInfoMap); + connectionCheck.checkConnections(); + + DbSchemaVersionCheck schemaVersionCheck = new DbSchemaVersionCheck(this.connetionInfoMap, + componentConfiguration); + schemaVersionCheck.checkSchemaVersion(); + + super.start(); + } + + public StrolchTransaction openTx() { + return openTx(StrolchTransaction.DEFAULT_REALM); + } + + @SuppressWarnings("resource") + // caller will/must close + public StrolchTransaction openTx(String realm) { +// PersistenceTransaction tx = this.persistenceManager.openTx(realm); +// XmlStrolchTransaction strolchTx = new XmlStrolchTransaction(tx); +// if (getContainer().hasComponent(ObserverHandler.class)) { +// strolchTx.setObserverHandler(getContainer().getComponent(ObserverHandler.class)); +// } +// return strolchTx; + return null; + } + + @Override + public OrderDao getOrderDao(StrolchTransaction tx) { + return new PostgreSqlOrderDao(tx); + } + + @Override + public ResourceDao getResourceDao(StrolchTransaction tx) { + return new PostgreSqlResourceDao(tx); + } +} diff --git a/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceDao.java b/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceDao.java new file mode 100644 index 000000000..eefe65b9d --- /dev/null +++ b/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceDao.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.persistence.postgresql; + +import li.strolch.model.Resource; +import li.strolch.model.Tags; +import li.strolch.persistence.api.ResourceDao; +import li.strolch.persistence.api.StrolchTransaction; + +public class PostgreSqlResourceDao extends AbstractDao implements ResourceDao { + + protected PostgreSqlResourceDao(StrolchTransaction tx) { + super(tx); + } + + @Override + protected String getClassType() { + return Tags.RESOURCE; + } +} diff --git a/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java b/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java new file mode 100644 index 000000000..a4b55b305 --- /dev/null +++ b/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java @@ -0,0 +1,102 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.persistence.postgresql; + +import li.strolch.persistence.api.StrolchPersistenceException; +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.persistence.api.TransactionCloseStrategy; +import li.strolch.runtime.observer.ObserverHandler; + +public class PostgreSqlStrolchTransaction implements StrolchTransaction { + + private ObserverHandler observerHandler; + private boolean suppressUpdates; + private TransactionCloseStrategy closeStrategy; +// private TransactionResult txResult; + + public PostgreSqlStrolchTransaction(/*PersistenceTransaction tx*/) { + this.suppressUpdates = false; +// this.tx = tx; + this.closeStrategy = TransactionCloseStrategy.COMMIT; + } + + /** + * @param observerHandler + * the observerHandler to set + */ + public void setObserverHandler(ObserverHandler observerHandler) { + this.observerHandler = observerHandler; + } + + /** + * @param suppressUpdates + * the suppressUpdates to set + */ + public void setSuppressUpdates(boolean suppressUpdates) { + this.suppressUpdates = suppressUpdates; + } + + /** + * @return the suppressUpdates + */ + public boolean isSuppressUpdates() { + return this.suppressUpdates; + } + + @Override + public void setCloseStrategy(TransactionCloseStrategy closeStrategy) { + this.closeStrategy = closeStrategy; + } + + @Override + public void autoCloseableCommit() { + + if (!this.suppressUpdates && this.observerHandler != null) { +// this.txResult = new TransactionResult(); +// this.tx.setTransactionResult(this.txResult); + } + +// this.tx.autoCloseableCommit(); + + if (!this.suppressUpdates && this.observerHandler != null) { + +// Set keys = this.txResult.getKeys(); +// for (String key : keys) { +// ModificationResult modificationResult = this.txResult.getModificationResult(key); +// +// this.observerHandler.add(key, modificationResult. getCreated()); +// this.observerHandler.update(key, modificationResult. getUpdated()); +// this.observerHandler.remove(key, modificationResult. getDeleted()); +// } + } + } + + @Override + public void autoCloseableRollback() { +// this.tx.autoCloseableRollback(); + } + + @Override + public void close() throws StrolchPersistenceException { + this.closeStrategy.close(this); + } + + @Override + public boolean isOpen() { +// return this.tx.isOpen(); + return true; + } +} diff --git a/src/main/resources/db_schema_0.1.0_drop.sql b/src/main/resources/db_schema_0.1.0_drop.sql new file mode 100644 index 000000000..fe35890fd --- /dev/null +++ b/src/main/resources/db_schema_0.1.0_drop.sql @@ -0,0 +1,5 @@ + +DROP TABLE IF EXISTS resources, orders, db_version; + +DROP TYPE IF EXISTS order_state; + diff --git a/src/main/resources/db_schema_0.1.0_initial.sql b/src/main/resources/db_schema_0.1.0_initial.sql new file mode 100644 index 000000000..808f6b97a --- /dev/null +++ b/src/main/resources/db_schema_0.1.0_initial.sql @@ -0,0 +1,33 @@ + +CREATE TABLE IF NOT EXISTS db_version ( + id SERIAL PRIMARY KEY, + version varchar(255), + description varchar(255), + created timestamp with time zone +); + +CREATE TABLE IF NOT EXISTS resources ( + id varchar(255) PRIMARY KEY, + name VARCHAR(255), + type VARCHAR(255), + asxml xml +); + +CREATE TYPE order_state AS ENUM ('CREATED', 'OPEN', 'EXECUTION', 'CLOSED'); + +CREATE TABLE IF NOT EXISTS orders ( + id varchar(255) PRIMARY KEY, + name VARCHAR(255), + type VARCHAR(255), + state order_state, + date timestamp with time zone, + asxml xml +); + +INSERT INTO db_version + (version, description, created) +values( + '0.1.0', + 'Initial schema version', + CURRENT_TIMESTAMP +); diff --git a/src/main/resources/db_version.properties b/src/main/resources/db_version.properties new file mode 100644 index 000000000..d0ef82ac6 --- /dev/null +++ b/src/main/resources/db_version.properties @@ -0,0 +1,2 @@ +# Property file defining what the currently expected version is supposed to be +db_version=0.1.0 \ No newline at end of file diff --git a/src/test/java/li/strolch/persistence/postgresql/dao/test/AbstractDaoImplTest.java b/src/test/java/li/strolch/persistence/postgresql/dao/test/AbstractDaoImplTest.java new file mode 100644 index 000000000..612806108 --- /dev/null +++ b/src/test/java/li/strolch/persistence/postgresql/dao/test/AbstractDaoImplTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.persistence.postgresql.dao.test; + +import java.io.File; + +import li.strolch.persistence.api.StrolchPersistenceHandler; +import li.strolch.testbase.runtime.RuntimeMock; + +import org.junit.AfterClass; +import org.junit.BeforeClass; + +/** + * @author Robert von Burg + * + */ +public abstract class AbstractDaoImplTest extends RuntimeMock { + + private static final String RUNTIME_PATH = "target/strolchRuntime/"; //$NON-NLS-1$ + private static final String DB_STORE_PATH_DIR = "dbStore"; //$NON-NLS-1$ + private static final String CONFIG_SRC = "src/test/resources/runtime/config"; //$NON-NLS-1$ + protected static StrolchPersistenceHandler persistenceHandler; + + @BeforeClass + public static void beforeClass() { + + File rootPath = new File(RUNTIME_PATH); + File configSrc = new File(CONFIG_SRC); + RuntimeMock.mockRuntime(rootPath, configSrc); + new File(rootPath, DB_STORE_PATH_DIR).mkdir(); + RuntimeMock.startContainer(rootPath); + + // initialize the component configuration + persistenceHandler = getContainer().getComponent(StrolchPersistenceHandler.class); + } + + @AfterClass + public static void afterClass() { + RuntimeMock.destroyRuntime(); + } +} diff --git a/src/test/java/li/strolch/persistence/postgresql/dao/test/ObserverUpdateTest.java b/src/test/java/li/strolch/persistence/postgresql/dao/test/ObserverUpdateTest.java new file mode 100644 index 000000000..a5dc772aa --- /dev/null +++ b/src/test/java/li/strolch/persistence/postgresql/dao/test/ObserverUpdateTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.persistence.postgresql.dao.test; + +import static li.strolch.model.ModelGenerator.createOrder; +import static li.strolch.model.ModelGenerator.createResource; +import static org.junit.Assert.assertEquals; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import li.strolch.model.Order; +import li.strolch.model.Resource; +import li.strolch.model.State; +import li.strolch.model.StrolchElement; +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.persistence.postgresql.ModificationResult; +import li.strolch.runtime.observer.Observer; +import li.strolch.runtime.observer.ObserverHandler; + +import org.junit.Test; + +/** + * @author Robert von Burg + * + */ +public class ObserverUpdateTest extends AbstractDaoImplTest { + + public final class ElementAddedObserver implements Observer { + + Map results = new HashMap<>(); + + private ModificationResult getModificationResult(String key) { + ModificationResult result = this.results.get(key); + if (result == null) { + result = new ModificationResult(key); + this.results.put(key, result); + } + return result; + } + + @Override + public void update(String key, List elements) { + getModificationResult(key).getUpdated().addAll(elements); + } + + @Override + public void remove(String key, List elements) { + getModificationResult(key).getDeleted().addAll(elements); + } + + @Override + public void add(String key, List elements) { + getModificationResult(key).getCreated().addAll(elements); + } + } + + @Test + public void shouldReceiveUpdates() { + + // register an observer for orders and resources + ElementAddedObserver observer = new ElementAddedObserver(); + getContainer().getComponent(ObserverHandler.class).registerObserver("Order", observer); //$NON-NLS-1$ + getContainer().getComponent(ObserverHandler.class).registerObserver("Resource", observer); //$NON-NLS-1$ + + // create order + Order newOrder = createOrder("MyTestOrder", "Test Name", "TestType", new Date(), State.CREATED); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + try (StrolchTransaction tx = persistenceHandler.openTx();) { + persistenceHandler.getOrderDao(tx).save(newOrder); + } + + // create resource + Resource newResource = createResource("MyTestResource", "Test Name", "TestType"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + try (StrolchTransaction tx = persistenceHandler.openTx();) { + persistenceHandler.getResourceDao(tx).save(newResource); + } + + assertEquals(2, observer.results.size()); + assertEquals(1, observer.results.get("Order").getCreated().size()); //$NON-NLS-1$ + assertEquals(1, observer.results.get("Resource").getCreated().size()); //$NON-NLS-1$ + + } +} diff --git a/src/test/java/li/strolch/persistence/postgresql/dao/test/PostgreSqlOrderDaoTest.java b/src/test/java/li/strolch/persistence/postgresql/dao/test/PostgreSqlOrderDaoTest.java new file mode 100644 index 000000000..205f3bd69 --- /dev/null +++ b/src/test/java/li/strolch/persistence/postgresql/dao/test/PostgreSqlOrderDaoTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.persistence.postgresql.dao.test; + +import static li.strolch.model.ModelGenerator.BAG_ID; +import static li.strolch.model.ModelGenerator.PARAM_STRING_ID; +import static li.strolch.model.ModelGenerator.createOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import li.strolch.model.Order; +import li.strolch.model.parameter.Parameter; +import li.strolch.persistence.api.StrolchTransaction; + +import org.junit.Test; + +public class PostgreSqlOrderDaoTest extends AbstractDaoImplTest { + + private static final String ID = "@testOrder"; //$NON-NLS-1$ + private static final String NAME = "Test Order"; //$NON-NLS-1$ + private static final String TYPE = "ToStock"; //$NON-NLS-1$ + + @Test + public void shouldCreateOrder() { + + // create + Order newOrder = createOrder("MyTestOrder", "Test Name", "TestType"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + try (StrolchTransaction tx = persistenceHandler.openTx();) { + persistenceHandler.getOrderDao(tx).save(newOrder); + } + } + + @Test + public void shouldCrud() { + + // create + Order newOrder = createOrder(ID, NAME, TYPE); + try (StrolchTransaction tx = persistenceHandler.openTx();) { + persistenceHandler.getOrderDao(tx).save(newOrder); + } + + // read + Order readOrder = null; + try (StrolchTransaction tx = persistenceHandler.openTx();) { + readOrder = persistenceHandler.getOrderDao(tx).queryBy(TYPE, ID); + } + assertNotNull("Should read Order with id " + ID, readOrder); //$NON-NLS-1$ + + // update + Parameter sParam = readOrder.getParameter(BAG_ID, PARAM_STRING_ID); + String newStringValue = "Giddiya!"; //$NON-NLS-1$ + sParam.setValue(newStringValue); + try (StrolchTransaction tx = persistenceHandler.openTx();) { + persistenceHandler.getOrderDao(tx).update(readOrder); + } + + // read updated + Order updatedOrder = null; + try (StrolchTransaction tx = persistenceHandler.openTx();) { + updatedOrder = persistenceHandler.getOrderDao(tx).queryBy(TYPE, ID); + } + assertNotNull("Should read Order with id " + ID, updatedOrder); //$NON-NLS-1$ + assertFalse("Objects can't be the same reference after re-reading!", readOrder == updatedOrder); //$NON-NLS-1$ + Parameter updatedParam = readOrder.getParameter(BAG_ID, PARAM_STRING_ID); + assertEquals(newStringValue, updatedParam.getValue()); + + // delete + try (StrolchTransaction tx = persistenceHandler.openTx();) { + persistenceHandler.getOrderDao(tx).remove(readOrder); + } + + // fail to re-read + try (StrolchTransaction tx = persistenceHandler.openTx();) { + Order order = persistenceHandler.getOrderDao(tx).queryBy(TYPE, ID); + assertNull("Should no read Order with id " + ID, order); //$NON-NLS-1$ + } + } +} diff --git a/src/test/java/li/strolch/persistence/postgresql/dao/test/PostgreSqlResourceDaoTest.java b/src/test/java/li/strolch/persistence/postgresql/dao/test/PostgreSqlResourceDaoTest.java new file mode 100644 index 000000000..f1154a1e2 --- /dev/null +++ b/src/test/java/li/strolch/persistence/postgresql/dao/test/PostgreSqlResourceDaoTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.persistence.postgresql.dao.test; + +import static li.strolch.model.ModelGenerator.BAG_ID; +import static li.strolch.model.ModelGenerator.PARAM_STRING_ID; +import static li.strolch.model.ModelGenerator.createResource; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import li.strolch.model.Resource; +import li.strolch.model.parameter.Parameter; +import li.strolch.persistence.api.StrolchTransaction; + +import org.junit.Test; + +public class PostgreSqlResourceDaoTest extends AbstractDaoImplTest { + + private static final String ID = "@testResource"; //$NON-NLS-1$ + private static final String NAME = "Test Resource"; //$NON-NLS-1$ + private static final String TYPE = "Box"; //$NON-NLS-1$ + + @Test + public void shouldCreateResource() { + + // create + Resource newResource = createResource("MyTestResource", "Test Name", "TestType"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + try (StrolchTransaction tx = persistenceHandler.openTx();) { + persistenceHandler.getResourceDao(tx).save(newResource); + } + } + + @Test + public void shouldCrud() { + + // create + Resource newResource = createResource(ID, NAME, TYPE); + try (StrolchTransaction tx = persistenceHandler.openTx();) { + persistenceHandler.getResourceDao(tx).save(newResource); + } + + // read + Resource readResource = null; + try (StrolchTransaction tx = persistenceHandler.openTx();) { + readResource = persistenceHandler.getResourceDao(tx).queryBy(TYPE, ID); + } + assertNotNull("Should read Resource with id " + ID, readResource); //$NON-NLS-1$ + + // update + Parameter sParam = readResource.getParameter(BAG_ID, PARAM_STRING_ID); + String newStringValue = "Giddiya!"; //$NON-NLS-1$ + sParam.setValue(newStringValue); + try (StrolchTransaction tx = persistenceHandler.openTx();) { + persistenceHandler.getResourceDao(tx).update(readResource); + } + + // read updated + Resource updatedResource = null; + try (StrolchTransaction tx = persistenceHandler.openTx();) { + updatedResource = persistenceHandler.getResourceDao(tx).queryBy(TYPE, ID); + } + assertNotNull("Should read Resource with id " + ID, updatedResource); //$NON-NLS-1$ + assertFalse("Objects can't be the same reference after re-reading!", readResource == updatedResource); //$NON-NLS-1$ + Parameter updatedParam = readResource.getParameter(BAG_ID, PARAM_STRING_ID); + assertEquals(newStringValue, updatedParam.getValue()); + + // delete + try (StrolchTransaction tx = persistenceHandler.openTx();) { + persistenceHandler.getResourceDao(tx).remove(readResource); + } + + // fail to re-read + try (StrolchTransaction tx = persistenceHandler.openTx();) { + Resource resource = persistenceHandler.getResourceDao(tx).queryBy(TYPE, ID); + assertNull("Should no read Resource with id " + ID, resource); //$NON-NLS-1$ + } + } +} diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml new file mode 100644 index 000000000..a35a3c351 --- /dev/null +++ b/src/test/resources/log4j.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/runtime/config/StrolchConfiguration.xml b/src/test/resources/runtime/config/StrolchConfiguration.xml new file mode 100644 index 000000000..26f7d59b0 --- /dev/null +++ b/src/test/resources/runtime/config/StrolchConfiguration.xml @@ -0,0 +1,27 @@ + + + + StrolchPersistenceTest + + EMPTY + true + + + + PersistenceHandler + li.strolch.persistence.api.StrolchPersistenceHandler + li.strolch.persistence.postgresql.PostgreSqlPersistenceHandler + + true + true + jdbc:postgresql://localhost/testdb + testuser + test + + + + ObserverHandler + li.strolch.runtime.observer.ObserverHandler + li.strolch.runtime.observer.DefaultObserverHandler + + \ No newline at end of file