diff --git a/li.strolch.persistence.postgresql/.gitignore b/li.strolch.persistence.postgresql/.gitignore new file mode 100644 index 000000000..b284c6517 --- /dev/null +++ b/li.strolch.persistence.postgresql/.gitignore @@ -0,0 +1,4 @@ +target/ +.project +.settings +.classpath diff --git a/li.strolch.persistence.postgresql/LICENSE b/li.strolch.persistence.postgresql/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/li.strolch.persistence.postgresql/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + 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 "[]" + 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 + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + 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. + 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. diff --git a/li.strolch.persistence.postgresql/README.md b/li.strolch.persistence.postgresql/README.md new file mode 100644 index 000000000..4988ce20f --- /dev/null +++ b/li.strolch.persistence.postgresql/README.md @@ -0,0 +1,72 @@ +li.strolch.persistence.postgresql +======================================================================= + +[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=li.strolch.persistence.postgresql)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.persistence.postgresql/) + +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 -u postgres psql + $ postgres=# + create user testUser with password 'test'; + create database testdb; + GRANT ALL PRIVILEGES ON DATABASE testdb to testuser; + GRANT CONNECT ON DATABASE testdb TO testuser ; + + # For tests: + create user testUser with password 'test'; + create database testdb; + GRANT ALL PRIVILEGES ON DATABASE testdb to testuser; + GRANT CONNECT ON DATABASE testdb TO testuser ; + + create user testuser1 with password 'test'; + create database testdb1; + GRANT ALL PRIVILEGES ON DATABASE testdb1 to testuser1; + GRANT CONNECT ON DATABASE testdb1 TO testuser1 ; + + create user testuser2 with password 'test'; + create database testdb2; + GRANT ALL PRIVILEGES ON DATABASE testdb2 to testuser2; + GRANT CONNECT ON DATABASE testdb2 TO testuser2 ; + +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/li.strolch.persistence.postgresql/pom.xml b/li.strolch.persistence.postgresql/pom.xml new file mode 100644 index 000000000..558f377b0 --- /dev/null +++ b/li.strolch.persistence.postgresql/pom.xml @@ -0,0 +1,102 @@ + + + + li.strolch + li.strolch.parent + 1.0.0-SNAPSHOT + ../li.strolch.parent/pom.xml + + + 4.0.0 + + li.strolch.persistence.postgresql + + li.strolch.persistence.postgresql + PostgreSQL Persistence Implementation for Strolch + + https://github.com/eitchnet/li.strolch.persistence.postgresql + + 2011 + + + Github Issues + https://github.com/eitchnet/li.strolch.persistence.postgresql/issues + + + + scm:git:https://github.com/eitchnet/li.strolch.persistence.postgresql.git + scm:git:git@github.com:eitch/li.strolch.persistence.postgresql.git + https://github.com/eitchnet/li.strolch.persistence.postgresql + + + + + + li.strolch + li.strolch.model + + + li.strolch + li.strolch.agent + + + org.postgresql + postgresql + 9.3-1100-jdbc41 + + + + + li.strolch + li.strolch.testbase + test + + + + + + + + src/main/resources + true + + **/componentVersion.properties + + + + src/main/resources + false + + **/componentVersion.properties + + + + + + org.codehaus.mojo + buildnumber-maven-plugin + + + 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/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/DaoCommand.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/DaoCommand.java new file mode 100644 index 000000000..a878f50c1 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/DaoCommand.java @@ -0,0 +1,26 @@ +/* + * 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.TransactionResult; + +/** + * @author Robert von Burg + */ +public interface DaoCommand { + + public void doComand(TransactionResult txResult); +} diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/DbConnectionCheck.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/DbConnectionCheck.java new file mode 100644 index 000000000..8fa034e5d --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/DbConnectionCheck.java @@ -0,0 +1,72 @@ +/* + * 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 li.strolch.persistence.api.DbConnectionInfo; +import li.strolch.runtime.configuration.StrolchConfigurationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @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()")) { //$NON-NLS-1$ + if (rs.next()) { + logger.info(MessageFormat.format("Connected to: {0}", rs.getString(1))); //$NON-NLS-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/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/DbSchemaVersionCheck.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/DbSchemaVersionCheck.java new file mode 100644 index 000000000..7ad6bc8cd --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/DbSchemaVersionCheck.java @@ -0,0 +1,205 @@ +/* + * 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(MessageFormat.format("[{0}] Checking Schema version for: {1}@{2}", realm, username, url)); + + try (Connection con = DriverManager.getConnection(url, username, password); + Statement st = con.createStatement();) { + + String expectedDbVersion = getExpectedDbVersion(); + + // first see if we have any schema + String msg = "select table_schema, table_name, table_type from information_schema.tables where table_name=''{0}'';"; + String checkSchemaExistsSql = MessageFormat.format(msg, 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)) { + String msg = "[{0}] Schema version {1} is the current version. No changes needed."; + msg = MessageFormat.format(msg, realm, currentVersion); + logger.info(msg); + } else { + String msg = "[{0}] Schema version is not current. Need to upgrade from {1} to {2}"; + msg = MessageFormat.format(msg, realm, currentVersion, expectedDbVersion); + logger.warn(msg); + upgradeSchema(realm, expectedDbVersion, st); + } + } + } + } + + public static String getExpectedDbVersion() { + Properties dbVersionProps = new Properties(); + try (InputStream stream = DbSchemaVersionCheck.class.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) { + String msg = "Expected resource file {0} does not exist or is not a valid properties file: {1}"; + msg = MessageFormat.format(msg, RESOURCE_DB_VERSION, e.getMessage()); + throw new StrolchException(msg, 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; + } + + public static String getSql(String dbVersion, String type) { + String schemaResourceS = MessageFormat.format("/db_schema_{0}_{1}.sql", dbVersion, type); + try (InputStream stream = DbSchemaVersionCheck.class.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) { + String msg = "[{0}] No schema exists, or is not valid. Schema generation is disabled, thus can not continue!"; + msg = MessageFormat.format(msg, realm); + throw new StrolchConfigurationException(msg); + } + + logger.info(MessageFormat.format("[{0}] Creating initial schema...", realm)); + + 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(MessageFormat.format("[{0}] Successfully created schema for version {1}", realm, dbVersion)); + } + + private void dropSchema(String realm, String dbVersion, Statement st) { + + if (!this.allowSchemaDrop) { + String msg = "[{0}] Dropping Schema is disabled, but is required to upgrade current schema..."; + msg = MessageFormat.format(msg, realm); + throw new StrolchConfigurationException(msg); + } + + logger.info(MessageFormat.format("[{0}] Dropping existing schema...", realm)); + + 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/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditDao.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditDao.java new file mode 100644 index 000000000..ad80954b2 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditDao.java @@ -0,0 +1,346 @@ +/* + * 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.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import li.strolch.model.audit.AccessType; +import li.strolch.model.audit.Audit; +import li.strolch.model.audit.AuditQuery; +import li.strolch.model.audit.AuditVisitor; +import li.strolch.persistence.api.AuditDao; +import li.strolch.persistence.api.StrolchPersistenceException; +import ch.eitchnet.utils.collections.DateRange; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + */ +public class PostgreSqlAuditDao implements AuditDao { + + public static final String ID = "id"; + public static final String ACCESS_TYPE = "access_type"; + public static final String ACCESS_TYPE_TYPE = "::access_type"; + public static final String ACTION = "action"; + public static final String NEW_VERSION = "new_version"; + public static final String ELEMENT_ACCESSED = "element_accessed"; + public static final String ELEMENT_TYPE = "element_type"; + public static final String DATE = "date"; + public static final String LASTNAME = "lastname"; + public static final String FIRSTNAME = "firstname"; + public static final String USERNAME = "username"; + public static final String FIELDS = StringHelper.commaSeparated(ID, USERNAME, FIRSTNAME, LASTNAME, DATE, + ELEMENT_TYPE, ELEMENT_ACCESSED, NEW_VERSION, ACTION, ACCESS_TYPE); + public static final String TABLE_NAME = "audits"; + + private PostgreSqlStrolchTransaction tx; + + /** + * @param postgreSqlStrolchTransaction + */ + public PostgreSqlAuditDao(PostgreSqlStrolchTransaction postgreSqlStrolchTransaction) { + this.tx = postgreSqlStrolchTransaction; + } + + @Override + public boolean hasElement(String type, Long id) { + String sql = "select count(*) from " + TABLE_NAME + " where " + ELEMENT_TYPE + " = ? and " + ID + " = ?"; //$NON-NLS-1$ + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + + statement.setString(1, type); + statement.setLong(2, id); + + try (ResultSet result = statement.executeQuery()) { + result.next(); + long numberOfElements = result.getLong(1); + if (numberOfElements == 0) + return false; + if (numberOfElements == 1) + return true; + + String msg = MessageFormat.format("Non unique number of elements with type {0} and id {1}", type, id); //$NON-NLS-1$ + throw new StrolchPersistenceException(msg); + } + + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query size due to: " + e.getMessage(), e); //$NON-NLS-1$ + } + } + + @Override + public long querySize(DateRange dateRange) { + String sql = "select count(*) from " + TABLE_NAME + " where " + DATE + " between ? and ?"; //$NON-NLS-1$ + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + + statement.setDate(1, new Date(dateRange.getFromDate().getTime()), Calendar.getInstance()); + statement.setDate(2, new Date(dateRange.getToDate().getTime()), Calendar.getInstance()); + + try (ResultSet result = statement.executeQuery()) { + result.next(); + return result.getLong(1); + } + + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query size due to: " + e.getMessage(), e); //$NON-NLS-1$ + } + } + + @Override + public long querySize(String type, DateRange dateRange) { + String sql = "select count(*) from " + TABLE_NAME + " where " + ELEMENT_TYPE + " = ? and " + DATE + " between ? and ?"; //$NON-NLS-1$ + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + + statement.setString(1, type); + statement.setDate(2, new Date(dateRange.getFromDate().getTime()), Calendar.getInstance()); + statement.setDate(3, new Date(dateRange.getToDate().getTime()), Calendar.getInstance()); + + try (ResultSet result = statement.executeQuery()) { + result.next(); + return result.getLong(1); + } + + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query size due to: " + e.getMessage(), e); //$NON-NLS-1$ + } + } + + @Override + public Set queryTypes() { + Set keySet = new HashSet<>(); + + String sql = "select distinct " + ELEMENT_TYPE + " from " + TABLE_NAME; //$NON-NLS-1$ + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + try (ResultSet result = statement.executeQuery()) { + while (result.next()) { + keySet.add(result.getString(ELEMENT_TYPE)); + } + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query types due to: " + e.getMessage(), e); //$NON-NLS-1$ + } + + return keySet; + } + + @Override + public Audit queryBy(String type, Long id) { + + String sql = "select " + FIELDS + " from " + TABLE_NAME + " where " + ELEMENT_TYPE + " = ? and " + ID + " = ?"; //$NON-NLS-1$ + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + + statement.setString(1, type); + statement.setLong(2, id); + + try (ResultSet result = statement.executeQuery()) { + if (!result.next()) { + return null; + } + Audit audit = auditFrom(result); + if (result.next()) + throw new StrolchPersistenceException("Non unique result for query: " + sql); //$NON-NLS-1$ + return audit; + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query types due to: " + e.getMessage(), e); //$NON-NLS-1$ + } + } + + @Override + public List queryAll(String type, DateRange dateRange) { + List list = new ArrayList<>(); + String sql = "select " + FIELDS + " from " + TABLE_NAME + " where " + ELEMENT_TYPE + " = ? and " + DATE + " between ? and ?"; //$NON-NLS-1$ + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + + statement.setString(1, type); + statement.setDate(2, new Date(dateRange.getFromDate().getTime()), Calendar.getInstance()); + statement.setDate(3, new Date(dateRange.getToDate().getTime()), Calendar.getInstance()); + + try (ResultSet result = statement.executeQuery()) { + while (result.next()) { + list.add(auditFrom(result)); + } + } + + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query types due to: " + e.getMessage(), e); //$NON-NLS-1$ + } + + return list; + } + + @Override + public void save(Audit audit) { + String sql = "insert into " + TABLE_NAME + " (" + FIELDS + ") values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?::access_type)"; //$NON-NLS-1$ + try (PreparedStatement preparedStatement = this.tx.getConnection().prepareStatement(sql)) { + + setAuditFields(audit, preparedStatement); + + int count = preparedStatement.executeUpdate(); + if (count != 1) { + throw new StrolchPersistenceException(MessageFormat.format( + "Expected to create 1 record, but created {0} for audit {2}", count, audit.getId())); //$NON-NLS-1$ + } + + } catch (SQLException e) { + throw new StrolchPersistenceException(MessageFormat.format("Failed to update Audit {0} due to {1}", audit, //$NON-NLS-1$ + e.getLocalizedMessage()), e); + } + } + + @Override + public void saveAll(List audits) { + for (Audit audit : audits) { + save(audit); + } + } + + @Override + public void update(Audit audit) { + String sql = "update " + TABLE_NAME + " set " + ID + " = ?, " + USERNAME + " = ?, " + FIRSTNAME + " = ?, " + + LASTNAME + " = ?, " + DATE + " = ?, " + ELEMENT_TYPE + " = ?, " + ELEMENT_ACCESSED + " = ?, " + + NEW_VERSION + " = ?, " + ACTION + " = ?, " + ACCESS_TYPE + " = ?::access_type where " + ID + " = ?"; //$NON-NLS-1$ + try (PreparedStatement preparedStatement = this.tx.getConnection().prepareStatement(sql)) { + + setAuditFields(audit, preparedStatement); + preparedStatement.setLong(11, audit.getId()); + + int count = preparedStatement.executeUpdate(); + if (count != 1) { + throw new StrolchPersistenceException(MessageFormat.format( + "Expected to update 1 record, but updated {0} for audit {2}", count, audit.getId())); //$NON-NLS-1$ + } + + } catch (SQLException e) { + throw new StrolchPersistenceException(MessageFormat.format("Failed to update Audit {0} due to {1}", audit, //$NON-NLS-1$ + e.getLocalizedMessage()), e); + } + } + + @Override + public void updateAll(List audits) { + for (Audit audit : audits) { + update(audit); + } + } + + @Override + public void remove(Audit audit) { + String sql = "delete from " + TABLE_NAME + " where " + ID + " = ?"; //$NON-NLS-1$ + try (PreparedStatement preparedStatement = this.tx.getConnection().prepareStatement(sql)) { + + preparedStatement.setLong(1, audit.getId()); + + int count = preparedStatement.executeUpdate(); + if (count != 1) { + String msg = "Expected to delete 1 audit with id {0} but deleted {1} elements!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, audit.getId(), count); + throw new StrolchPersistenceException(msg); + } + + } catch (SQLException e) { + throw new StrolchPersistenceException(MessageFormat.format("Failed to remove {0} due to {2}", //$NON-NLS-1$ + audit.getId(), e.getLocalizedMessage()), e); + } + } + + @Override + public void removeAll(List audits) { + for (Audit audit : audits) { + remove(audit); + } + } + + @Override + public long removeAll(String type, DateRange dateRange) { + String sql = "delete from " + TABLE_NAME + " where " + ELEMENT_TYPE + " = ? and " + DATE + " between ? and ?"; //$NON-NLS-1$ + try (PreparedStatement preparedStatement = this.tx.getConnection().prepareStatement(sql)) { + + preparedStatement.setString(1, type); + preparedStatement.setDate(2, new Date(dateRange.getFromDate().getTime()), Calendar.getInstance()); + preparedStatement.setDate(3, new Date(dateRange.getToDate().getTime()), Calendar.getInstance()); + + int modCount = preparedStatement.executeUpdate(); + return modCount; + + } catch (SQLException e) { + throw new StrolchPersistenceException(MessageFormat.format("Failed to remove all elements due to {0}", //$NON-NLS-1$ + e.getLocalizedMessage()), e); + } + } + + @Override + public List doQuery(AuditQuery query, AuditVisitor auditVisitor) { + + PostgreSqlAuditQueryVisitor queryVisitor = new PostgreSqlAuditQueryVisitor(FIELDS); + query.accept(queryVisitor); + return null; + } + + private void setAuditFields(Audit audit, PreparedStatement ps) throws SQLException { + + // 1 id = ?, + // 2 username = ?, + // 3 firstname = ?, + // 4 lastname = ?, + // 5 date = ?, + // 6 element_type = ?, + // 7 element_accessed = ?, + // 8 new_version = ?, + // 9 action = ?, + // 10 access_type = ?::access_type + + ps.setLong(1, audit.getId()); + ps.setString(2, audit.getUsername()); + ps.setString(3, audit.getFirstname()); + ps.setString(4, audit.getLastname()); + ps.setDate(5, new Date(audit.getDate().getTime()), Calendar.getInstance()); + ps.setString(6, audit.getElementType()); + ps.setString(7, audit.getElementAccessed()); + + if (audit.getNewVersion() == null) + ps.setDate(8, null); + else + ps.setDate(8, new Date(audit.getNewVersion().getTime()), Calendar.getInstance()); + + ps.setString(9, audit.getAction()); + ps.setString(10, audit.getAccessType().name()); + } + + private Audit auditFrom(ResultSet resultSet) throws SQLException { + + Audit audit = new Audit(); + audit.setId(resultSet.getLong(1)); + audit.setUsername(resultSet.getString(2)); + audit.setFirstname(resultSet.getString(3)); + audit.setLastname(resultSet.getString(4)); + audit.setDate(resultSet.getDate(5)); + audit.setElementType(resultSet.getString(6)); + audit.setElementAccessed(resultSet.getString(7)); + audit.setNewVersion(resultSet.getDate(8)); + audit.setAction(resultSet.getString(9)); + audit.setAccessType(AccessType.valueOf(resultSet.getString(10))); + return audit; + } +} diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditQueryVisitor.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditQueryVisitor.java new file mode 100644 index 000000000..379c40407 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditQueryVisitor.java @@ -0,0 +1,160 @@ +/* + * 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.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import li.strolch.model.audit.AccessType; +import li.strolch.model.audit.ActionSelection; +import li.strolch.model.audit.AuditQuery; +import li.strolch.model.audit.AuditQueryVisitor; +import li.strolch.model.audit.ElementSelection; +import li.strolch.model.audit.IdentitySelection; +import li.strolch.model.query.StringSelection; +import ch.eitchnet.utils.StringMatchMode; + +/** + * @author Robert von Burg + */ +public class PostgreSqlAuditQueryVisitor implements AuditQueryVisitor { + + protected StringBuilder sql; + protected StringBuilder sb; + protected List values; + protected String indent; + private String sqlAsString; + + /** + * @param fields + */ + public PostgreSqlAuditQueryVisitor(String fields) { + this.indent = ""; + this.sql = new StringBuilder(); + this.sb = new StringBuilder(); + this.values = new ArrayList<>(); + + this.sql.append("select "); + this.sql.append(fields); + this.sql.append("\nfrom\n"); + this.sql.append(" "); + this.sql.append(PostgreSqlAuditDao.TABLE_NAME); + this.indent = " "; + } + + public String getSql() { + if (this.sqlAsString != null) + return this.sqlAsString; + + this.sql.append("\nwhere\n"); + this.sql.append(this.sb.toString()); + this.sqlAsString = this.sql.toString(); + return this.sqlAsString; + } + + @Override + public void visit(AuditQuery auditQuery) { + ensureAnd(); + this.sb.append(this.indent); + this.sb.append(PostgreSqlAuditDao.ELEMENT_TYPE); + this.sb.append(" = ?\n"); + ensureAnd(); + this.values.add(auditQuery.getElementTypeSelection()); + PostgreSqlHelper.toSql(this.indent, this.sb, this.values, PostgreSqlAuditDao.DATE, auditQuery.getDateRange()); + } + + @Override + public void visit(ElementSelection selection) { + if (!selection.isElementsAccessedWildcard()) { + StringSelection sel = selection.getElementAccessedSelection(); + toSql(PostgreSqlAuditDao.ELEMENT_ACCESSED, sel.getMatchMode(), sel.getValues()); + } + } + + @Override + public void visit(IdentitySelection selection) { + if (selection.isWildcard()) + return; + + if (!selection.isFirstnameWildcard()) { + StringSelection sel = selection.getFirstnameSelection(); + toSql(PostgreSqlAuditDao.FIRSTNAME, sel.getMatchMode(), sel.getValues()); + } + + if (!selection.isLastnameWildcard()) { + StringSelection sel = selection.getLastnameSelection(); + toSql(PostgreSqlAuditDao.LASTNAME, sel.getMatchMode(), sel.getValues()); + } + + if (!selection.isUsernameWildcard()) { + StringSelection sel = selection.getUsernameSelection(); + toSql(PostgreSqlAuditDao.USERNAME, sel.getMatchMode(), sel.getValues()); + } + } + + @Override + public void visit(ActionSelection selection) { + if (!selection.isWildcardAction()) { + StringSelection sel = selection.getActionSelection(); + toSql(PostgreSqlAuditDao.ACTION, sel.getMatchMode(), sel.getValues()); + } + + if (!selection.isWildcardActionType()) { + + AccessType[] accessTypes = selection.getAccessTypes(); + ensureAnd(); + this.sb.append(this.indent); + if (accessTypes.length == 1) { + this.sb.append(PostgreSqlAuditDao.ACCESS_TYPE + " = ?"); + this.sb.append(PostgreSqlAuditDao.ACCESS_TYPE_TYPE); + this.sb.append("\n"); + this.values.add(accessTypes[0].name()); + } else { + this.sb.append(PostgreSqlAuditDao.ACCESS_TYPE + " in ("); + for (int i = 0; i < accessTypes.length; i++) { + this.sb.append("?"); + this.sb.append(PostgreSqlAuditDao.ACCESS_TYPE_TYPE); + this.values.add(accessTypes[i].name()); + if (i < accessTypes.length - 1) + this.sb.append(", "); + } + this.sb.append(" )\n"); + this.sb.append("\n"); + } + } + } + + private void toSql(String column, StringMatchMode mm, String[] values) { + ensureAnd(); + this.sb.append(this.indent); + this.sb.append(PostgreSqlHelper.toSql(column, this.indent, mm, this.values, values)); + } + + public void setValues(PreparedStatement ps) throws SQLException { + for (int i = 0; i < this.values.size(); i++) { + ps.setObject(i + 1, this.values.get(i)); + } + } + + private void ensureAnd() { + if (this.sb.length() > 0) { + this.sb.append(this.indent); + this.sb.append("and \n"); + } + } +} diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlHelper.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlHelper.java new file mode 100644 index 000000000..b552d9e2d --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlHelper.java @@ -0,0 +1,168 @@ +/* + * 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.Date; +import java.util.ArrayList; +import java.util.List; + +import ch.eitchnet.utils.StringMatchMode; +import ch.eitchnet.utils.collections.DateRange; + +/** + * @author Robert von Burg + */ +public class PostgreSqlHelper { + + public static void toSql(String indent, StringBuilder sb, List values, String column, DateRange dateRange) { + + // TODO handle inclusive/exclusive: between is inclusive + + if (dateRange.isDate()) { + sb.append(indent); + sb.append(column); + sb.append(" = ?\n"); + values.add(new Date(dateRange.getFromDate().getTime())); + } else if (dateRange.isBounded()) { + sb.append(indent); + sb.append(column); + sb.append(" between ? and ?\n"); + values.add(new Date(dateRange.getFromDate().getTime())); + values.add(new Date(dateRange.getToDate().getTime())); + } else if (dateRange.isToBounded()) { + sb.append(indent); + sb.append(column); + sb.append(" <= ?\n"); + values.add(new Date(dateRange.getToDate().getTime())); + } else if (dateRange.isFromBounded()) { + sb.append(indent); + sb.append(column); + sb.append(" >= ?\n"); + values.add(new Date(dateRange.getFromDate().getTime())); + } + } + + public static String toSql(String column, String indent, StringMatchMode mm, List values, String... query) { + + // CS EQ + // 1. x x + // 2. x o + // 3. o x + // 4. o o + + StringBuilder sb = new StringBuilder(); + if (mm.isCaseSensitve() && mm.isEquals()) { + if (query.length == 1) { + sb.append(column + " = ?\n"); + values.add(query[0]); + } else { + sb.append(column + " in ( "); + for (int i = 0; i < query.length; i++) { + sb.append("?"); + values.add(query[i]); + if (i < query.length - 1) + sb.append(", "); + } + sb.append(" )\n"); + } + } else if (!mm.isCaseSensitve() && mm.isEquals()) { + if (query.length == 1) { + sb.append("lower(" + column + ") = ?\n"); + values.add(query[0].toLowerCase()); + } else { + sb.append("lower(" + column + ") in ( "); + for (int i = 0; i < query.length; i++) { + sb.append("?"); + values.add(query[i].toLowerCase()); + if (i < query.length - 1) + sb.append(", "); + } + sb.append(" )\n"); + } + } else if (!mm.isEquals() && mm.isCaseSensitve()) { + if (query.length == 1) { + sb.append(column + " like ?\n"); + values.add("%" + query[0] + "%"); + } else { + sb.append("(\n"); + for (int i = 0; i < query.length; i++) { + sb.append(indent); + sb.append(" "); + sb.append(column + " like ?"); + values.add("%" + query[i] + "%"); + if (i < query.length - 1) + sb.append(" or"); + sb.append("\n"); + } + sb.append(")\n"); + } + } else { + if (query.length == 1) { + sb.append("lower(" + column + ") like ?\n"); + values.add("%" + query[0].toLowerCase() + "%"); + } else { + sb.append("(\n"); + for (int i = 0; i < query.length; i++) { + sb.append(indent); + sb.append(" "); + sb.append("lower(" + column + ") like ?"); + values.add("%" + query[i].toLowerCase() + "%"); + if (i < query.length - 1) + sb.append(" or"); + sb.append("\n"); + } + sb.append(")\n"); + } + } + + return sb.toString(); + } + + public static void main(String[] args) { + ArrayList values = new ArrayList<>(); + String sql = toSql("name", " ", StringMatchMode.CONTAINS_CASE_INSENSITIVE, values, "foo", "bar", "fub"); + System.out.println(sql); + System.out.println(); + + sql = toSql("name", " ", StringMatchMode.CONTAINS_CASE_INSENSITIVE, values, "foo"); + System.out.println(sql); + System.out.println(); + + sql = toSql("name", " ", StringMatchMode.CONTAINS_CASE_SENSITIVE, values, "foo", "bar", "fub"); + System.out.println(sql); + System.out.println(); + + sql = toSql("name", " ", StringMatchMode.CONTAINS_CASE_SENSITIVE, values, "foo"); + System.out.println(sql); + System.out.println(); + + sql = toSql("name", " ", StringMatchMode.EQUALS_CASE_INSENSITIVE, values, "foo", "bar", "fub"); + System.out.println(sql); + System.out.println(); + + sql = toSql("name", " ", StringMatchMode.EQUALS_CASE_INSENSITIVE, values, "foo"); + System.out.println(sql); + System.out.println(); + + sql = toSql("name", " ", StringMatchMode.EQUALS_CASE_SENSITIVE, values, "foo", "bar", "fub"); + System.out.println(sql); + System.out.println(); + + sql = toSql("name", " ", StringMatchMode.EQUALS_CASE_SENSITIVE, values, "foo"); + System.out.println(sql); + System.out.println(); + } +} diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderDao.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderDao.java new file mode 100644 index 000000000..d5268098b --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderDao.java @@ -0,0 +1,189 @@ +/* + * 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 exporders 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.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.sax.SAXResult; + +import li.strolch.model.Order; +import li.strolch.model.OrderVisitor; +import li.strolch.model.Tags; +import li.strolch.model.query.OrderQuery; +import li.strolch.model.xml.OrderToSaxVisitor; +import li.strolch.model.xml.SimpleStrolchElementListener; +import li.strolch.model.xml.XmlModelSaxReader; +import li.strolch.persistence.api.OrderDao; +import li.strolch.persistence.api.StrolchPersistenceException; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +@SuppressWarnings("nls") +public class PostgreSqlOrderDao extends PostgresqlDao implements OrderDao { + + public static final String ORDERS = "orders"; + + /** + * @param tx + */ + public PostgreSqlOrderDao(PostgreSqlStrolchTransaction tx) { + super(tx); + } + + @Override + protected String getClassName() { + return Tags.ORDER; + } + + @Override + protected String getTableName() { + return ORDERS; + } + + @Override + protected Order parseFromXml(String id, String type, SQLXML sqlxml) { + SimpleStrolchElementListener listener = new SimpleStrolchElementListener(); + try (InputStream binaryStream = sqlxml.getBinaryStream()) { + SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); + parser.parse(binaryStream, new XmlModelSaxReader(listener)); + } catch (SQLException | IOException | SAXException | ParserConfigurationException e) { + throw new StrolchPersistenceException(MessageFormat.format( + "Failed to extract Order from sqlxml value for {0} / {1}", id, type)); + } + + if (listener.getOrders().size() == 0) + throw new StrolchPersistenceException(MessageFormat.format( + "No Orders parsed from sqlxml value for {0} / {1}", id, type)); + if (listener.getOrders().size() > 1) + throw new StrolchPersistenceException(MessageFormat.format( + "Multiple Orders parsed from sqlxml value for {0} / {1}", id, type)); + + return listener.getOrders().get(0); + } + + protected SQLXML createSqlXml(Order order, PreparedStatement preparedStatement) throws SQLException, SAXException { + SQLXML sqlxml = this.tx.getConnection().createSQLXML(); + SAXResult saxResult = sqlxml.setResult(SAXResult.class); + ContentHandler contentHandler = saxResult.getHandler(); + contentHandler.startDocument(); + new OrderToSaxVisitor(contentHandler).visit(order); + contentHandler.endDocument(); + return sqlxml; + } + + @Override + protected void internalSave(final Order order) { + String sql = "insert into " + getTableName() + + " (id, name, type, state, date, asxml) values (?, ?, ?, ?::order_state, ?, ?)"; + try (PreparedStatement preparedStatement = PostgreSqlOrderDao.this.tx.getConnection().prepareStatement(sql)) { + preparedStatement.setString(1, order.getId()); + preparedStatement.setString(2, order.getName()); + preparedStatement.setString(3, order.getType()); + preparedStatement.setString(4, order.getState().name()); + preparedStatement.setDate(5, new Date(order.getDate().getTime()), Calendar.getInstance()); + + SQLXML sqlxml = createSqlXml(order, preparedStatement); + preparedStatement.setSQLXML(6, sqlxml); + try { + int modCount = preparedStatement.executeUpdate(); + if (modCount != 1) { + String msg = "Expected to save 1 element with id {0} but SQL statement modified {1} elements!"; + msg = MessageFormat.format(msg, order.getId(), modCount); + throw new StrolchPersistenceException(msg); + } + } finally { + sqlxml.free(); + } + + } catch (SQLException | SAXException e) { + throw new StrolchPersistenceException(MessageFormat.format("Failed to insert Order {0} due to {1}", + order.getLocator(), e.getLocalizedMessage()), e); + } + } + + @Override + protected void internalUpdate(final Order order) { + String sql = "update " + getTableName() + + " set name = ?, type = ?, state = ?::order_state, date = ?, asxml = ? where id = ? "; + try (PreparedStatement preparedStatement = PostgreSqlOrderDao.this.tx.getConnection().prepareStatement(sql)) { + + preparedStatement.setString(1, order.getName()); + preparedStatement.setString(2, order.getType()); + preparedStatement.setString(3, order.getState().name()); + preparedStatement.setDate(4, new Date(order.getDate().getTime()), Calendar.getInstance()); + preparedStatement.setString(6, order.getId()); + + SQLXML sqlxml = createSqlXml(order, preparedStatement); + preparedStatement.setSQLXML(5, sqlxml); + try { + int modCount = preparedStatement.executeUpdate(); + if (modCount != 1) { + String msg = "Expected to update 1 element with id {0} but SQL statement modified {1} elements!"; + msg = MessageFormat.format(msg, order.getId(), modCount); + throw new StrolchPersistenceException(msg); + } + } finally { + sqlxml.free(); + } + + } catch (SQLException | SAXException e) { + throw new StrolchPersistenceException(MessageFormat.format("Failed to update Order {0} due to {1}", + order.getLocator(), e.getLocalizedMessage()), e); + } + } + + @Override + public List doQuery(OrderQuery query, OrderVisitor orderVisitor) { + + PostgreSqlOrderQueryVisitor queryVisitor = new PostgreSqlOrderQueryVisitor("id, asxml"); + query.accept(queryVisitor); + queryVisitor.validate(); + + List list = new ArrayList<>(); + + String sql = queryVisitor.getSql(); + try (PreparedStatement ps = PostgreSqlOrderDao.this.tx.getConnection().prepareStatement(sql)) { + queryVisitor.setValues(ps); + + try (ResultSet result = ps.executeQuery()) { + while (result.next()) { + String id = result.getString("id"); + SQLXML sqlxml = result.getSQLXML("asxml"); + Order t = parseFromXml(id, queryVisitor.getType(), sqlxml); + list.add(orderVisitor.visit(t)); + } + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to perform query due to: " + e.getMessage(), e); + } + + return list; + } +} diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderQueryVisitor.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderQueryVisitor.java new file mode 100644 index 000000000..40050292a --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlOrderQueryVisitor.java @@ -0,0 +1,56 @@ +/* + * 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.Tags; +import li.strolch.model.query.DateSelection; +import li.strolch.model.query.OrderQueryVisitor; +import li.strolch.model.query.StateSelection; + +/** + * @author Robert von Burg + */ +public class PostgreSqlOrderQueryVisitor extends PostgreSqlQueryVisitor implements OrderQueryVisitor { + + /** + * @param fields + */ + public PostgreSqlOrderQueryVisitor(String fields) { + super(fields); + } + + @Override + protected String getClassName() { + return Tags.ORDER; + } + + @Override + protected String getTableName() { + return PostgreSqlOrderDao.ORDERS; + } + + @Override + public void visit(DateSelection selection) { + PostgreSqlHelper.toSql(this.indent, this.sb, this.values, "date", selection.getDateRange()); + } + + @Override + public void visit(StateSelection selection) { + this.sb.append(this.indent); + this.sb.append("state = ?::order_state\n"); + this.values.add(selection.getState().name()); + } +} diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlPersistenceHandler.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlPersistenceHandler.java new file mode 100644 index 000000000..3b04443d7 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlPersistenceHandler.java @@ -0,0 +1,171 @@ +/* + * 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 static ch.eitchnet.utils.helper.StringHelper.DOT; + +import java.sql.Connection; +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 java.util.Set; + +import li.strolch.agent.api.ComponentContainer; +import li.strolch.agent.api.StrolchComponent; +import li.strolch.agent.api.StrolchRealm; +import li.strolch.persistence.api.AuditDao; +import li.strolch.persistence.api.DbConnectionInfo; +import li.strolch.persistence.api.OrderDao; +import li.strolch.persistence.api.PersistenceHandler; +import li.strolch.persistence.api.ResourceDao; +import li.strolch.persistence.api.StrolchPersistenceException; +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.runtime.StrolchConstants; +import li.strolch.runtime.configuration.ComponentConfiguration; +import li.strolch.runtime.configuration.StrolchConfigurationException; +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author Robert von Burg + */ +public class PostgreSqlPersistenceHandler extends StrolchComponent implements PersistenceHandler { + + private static final String PROP_DB_URL = "db.url"; //$NON-NLS-1$ + private static final String PROP_DB_USERNAME = "db.username"; //$NON-NLS-1$ + private static final String PROP_DB_PASSWORD = "db.password"; //$NON-NLS-1$ + + 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<>(); + + Set realmNames = getContainer().getRealmNames(); + for (String realmName : realmNames) { + + StrolchRealm realm = getContainer().getRealm(realmName); + if (realm.getMode().isTransient()) + continue; + + String dbUrlKey = PROP_DB_URL; + String dbUsernameKey = PROP_DB_USERNAME; + String dbPasswordKey = PROP_DB_PASSWORD; + if (!realmName.equals(StrolchConstants.DEFAULT_REALM)) { + dbUrlKey += DOT + realmName; + dbUsernameKey += DOT + realmName; + dbPasswordKey += DOT + realmName; + } + + String dbUrl = componentConfiguration.getString(dbUrlKey, null); + String username = componentConfiguration.getString(dbUsernameKey, null); + String password = componentConfiguration.getString(dbPasswordKey, null); + + DbConnectionInfo connectionInfo = new DbConnectionInfo(realmName, dbUrl); + connectionInfo.setUsername(username); + connectionInfo.setPassword(password); + + loadDriverForConnection(connectionInfo); + this.connetionInfoMap.put(realmName, connectionInfo); + } + + super.initialize(componentConfiguration); + } + + private void loadDriverForConnection(DbConnectionInfo connectionInfo) { + Driver driver; + try { + // server loader does not seem to work in all contexts, thus: + org.postgresql.Driver.getLogLevel(); + + driver = DriverManager.getDriver(connectionInfo.getUrl()); + } catch (SQLException e) { + String msg = "Failed to load DB driver for URL {0} due to: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, connectionInfo.getUrl(), e.getMessage()); + throw new StrolchConfigurationException(msg, e); + } + + String compliant = driver.jdbcCompliant() ? "" : "non"; //$NON-NLS-1$ //$NON-NLS-2$ + String msg = "Realm {0}: Using {1} JDBC compliant Driver {2}.{3}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, connectionInfo.getRealm(), compliant, driver.getMajorVersion(), + driver.getMinorVersion()); + logger.info(msg); + + } + + @Override + public void start() { + + // test all connections + DbConnectionCheck connectionCheck = new DbConnectionCheck(this.connetionInfoMap); + connectionCheck.checkConnections(); + + DbSchemaVersionCheck schemaVersionCheck = new DbSchemaVersionCheck(this.connetionInfoMap, + this.componentConfiguration); + schemaVersionCheck.checkSchemaVersion(); + + super.start(); + } + + @Override + public StrolchTransaction openTx(StrolchRealm realm, Certificate certificate, String action) { + return new PostgreSqlStrolchTransaction(getContainer().getPrivilegeHandler(), realm, certificate, action, this); + } + + Connection getConnection(String realm) { + DbConnectionInfo dbInfo = this.connetionInfoMap.get(realm); + if (dbInfo == null) { + String msg = MessageFormat.format("There is no connection registered for the realm {0}", realm); //$NON-NLS-1$ + throw new StrolchPersistenceException(msg); + } + + try { + String url = dbInfo.getUrl(); + String username = dbInfo.getUsername(); + String password = dbInfo.getPassword(); + Connection connection = DriverManager.getConnection(url, username, password); + connection.setAutoCommit(false); + return connection; + } catch (SQLException e) { + String msg = MessageFormat.format("Failed to get a connection for {0} due to {1}", dbInfo, e.getMessage()); //$NON-NLS-1$ + throw new StrolchPersistenceException(msg, e); + } + } + + @Override + public OrderDao getOrderDao(StrolchTransaction tx) { + return ((PostgreSqlStrolchTransaction) tx).getOrderDao(); + } + + @Override + public ResourceDao getResourceDao(StrolchTransaction tx) { + return ((PostgreSqlStrolchTransaction) tx).getResourceDao(); + } + + @Override + public AuditDao getAuditDao(StrolchTransaction tx) { + return ((PostgreSqlStrolchTransaction) tx).getAuditDao(); + } +} diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlQueryVisitor.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlQueryVisitor.java new file mode 100644 index 000000000..6051c7f53 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlQueryVisitor.java @@ -0,0 +1,354 @@ +/* + * 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 static li.strolch.persistence.postgresql.PostgreSqlHelper.toSql; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import li.strolch.model.query.AndSelection; +import li.strolch.model.query.IdSelection; +import li.strolch.model.query.NameSelection; +import li.strolch.model.query.NotSelection; +import li.strolch.model.query.OrSelection; +import li.strolch.model.query.ParameterBagSelection; +import li.strolch.model.query.ParameterBagSelection.NullParameterBagSelection; +import li.strolch.model.query.ParameterSelection.BooleanParameterSelection; +import li.strolch.model.query.ParameterSelection.DateParameterSelection; +import li.strolch.model.query.ParameterSelection.DateRangeParameterSelection; +import li.strolch.model.query.ParameterSelection.FloatParameterSelection; +import li.strolch.model.query.ParameterSelection.IntegerParameterSelection; +import li.strolch.model.query.ParameterSelection.LongParameterSelection; +import li.strolch.model.query.ParameterSelection.NullParameterSelection; +import li.strolch.model.query.ParameterSelection.StringListParameterSelection; +import li.strolch.model.query.ParameterSelection.StringParameterSelection; +import li.strolch.model.query.ParameterSelectionVisitor; +import li.strolch.model.query.Selection; +import li.strolch.model.query.StrolchRootElementSelectionVisitor; +import li.strolch.model.query.StrolchTypeNavigation; +import ch.eitchnet.utils.StringMatchMode; +import ch.eitchnet.utils.dbc.DBC; +import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; + +/** + * @author Robert von Burg + */ +public abstract class PostgreSqlQueryVisitor implements StrolchRootElementSelectionVisitor, ParameterSelectionVisitor { + + protected StringBuilder sql; + protected StringBuilder sb; + protected String type; + protected List values; + protected boolean any; + protected String indent; + private String sqlAsString; + + public PostgreSqlQueryVisitor(String fields) { + this.indent = ""; + this.sql = new StringBuilder(); + this.sb = new StringBuilder(); + this.values = new ArrayList<>(); + + this.sql.append("select "); + this.sql.append(fields); + this.sql.append("\nfrom\n"); + this.sql.append(" "); + this.sql.append(getTableName()); + this.indent = " "; + } + + public String getSql() { + if (this.sqlAsString != null) + return this.sqlAsString; + + this.sql.append("\nwhere\n"); + this.sql.append(this.indent); + + if (this.any) { + this.sql.append("type = ?"); + this.sqlAsString = this.sql.toString(); + return this.sqlAsString; + } + + this.sql.append("type = ? and\n"); + + this.sql.append(this.sb.toString()); + this.sqlAsString = this.sql.toString(); + return this.sqlAsString; + } + + /** + * @return the any + */ + public boolean isAny() { + return this.any; + } + + public String getType() { + return this.type; + } + + public void validate() { + DBC.INTERIM.assertNotEmpty("No navigation was set!", this.type); + } + + protected abstract String getClassName(); + + protected abstract String getTableName(); + + @Override + public void visit(StrolchTypeNavigation navigation) { + this.type = navigation.getType(); + } + + @Override + public void visit(IdSelection selection) { + this.sb.append(this.indent); + List ids = selection.getIds(); + if (ids.isEmpty()) + return; + int size = ids.size(); + if (size == 1) { + this.sb.append("id = ?\n"); + this.values.add(ids.get(0)); + } else { + this.sb.append("id in ("); + Iterator iter = ids.iterator(); + while (iter.hasNext()) { + String id = iter.next(); + this.sb.append("?"); + this.values.add(id); + if (iter.hasNext()) + this.sb.append(", "); + } + this.sb.append(" )\n"); + } + } + + @Override + public void visit(NameSelection selection) { + this.sb.append(this.indent); + String name = selection.getName(); + StringMatchMode mm = selection.getMatchMode(); + this.sb.append(toSql("name", this.indent, mm, this.values, name)); + } + + @Override + public void visitAny() { + this.any = true; + } + + @Override + public void visitAnd(AndSelection andSelection) { + this.sb.append(this.indent); + List selections = andSelection.getSelections(); + this.sb.append("( \n"); + Iterator iter = selections.iterator(); + String indent = this.indent; + this.indent += " "; + while (iter.hasNext()) { + Selection selection = iter.next(); + selection.accept(this); + if (iter.hasNext()) { + this.sb.append(indent); + this.sb.append("and\n"); + } + } + this.indent = indent; + this.sb.append(this.indent); + this.sb.append(")\n"); + } + + @Override + public void visitOr(OrSelection orSelection) { + this.sb.append(this.indent); + List selections = orSelection.getSelections(); + this.sb.append("( \n"); + Iterator iter = selections.iterator(); + String indent = this.indent; + this.indent += " "; + while (iter.hasNext()) { + Selection selection = iter.next(); + selection.accept(this); + if (iter.hasNext()) { + this.sb.append(indent); + this.sb.append("or\n"); + } + } + this.indent = indent; + this.sb.append(this.indent); + this.sb.append(")\n"); + } + + @Override + public void visitNot(NotSelection notSelection) { + this.sb.append(this.indent); + List selections = notSelection.getSelections(); + this.sb.append("not ( \n"); + Iterator iter = selections.iterator(); + String indent = this.indent; + this.indent += " "; + while (iter.hasNext()) { + Selection selection = iter.next(); + selection.accept(this); + if (iter.hasNext()) { + this.sb.append(indent); + this.sb.append("and\n"); + } + } + this.indent = indent; + this.sb.append(this.indent); + this.sb.append(")\n"); + } + + private void xpath(String bagKey, String paramKey, String paramValue) { + String xpath = "cast(xpath('//Resource/ParameterBag[@Id=\"${bagKey}\"]/Parameter[@Id=\"${paramKey}\" and @Value=\"${paramValue}\"]', asxml) as text[]) != '{}'\n"; + this.sb.append(this.indent); + xpath = xpath.replace("${bagKey}", bagKey); + xpath = xpath.replace("${paramKey}", paramKey); + xpath = xpath.replace("${paramValue}", paramValue); + this.sb.append(xpath); + } + + @Override + public void visit(StringParameterSelection selection) { + String value = selection.getValue(); + + String xpath = "xpath('//Resource/ParameterBag[@Id=\"${bagKey}\"]/Parameter[@Id=\"${paramKey}\"]/@Value', asxml))::TEXT AS content"; + xpath = xpath.replace("${bagKey}", selection.getBagKey()); + xpath = xpath.replace("${paramKey}", selection.getParamKey()); + + this.sb.append(this.indent); + this.sb.append("id in (\n"); + this.sb.append(this.indent); + this.sb.append(" SELECT id\n"); + this.sb.append(this.indent); + this.sb.append(" FROM (\n"); + this.sb.append(this.indent); + this.sb.append(" SELECT id, UNNEST("); + this.sb.append(xpath); + this.sb.append("\n"); + this.sb.append(this.indent); + this.sb.append("from "); + this.sb.append(getTableName()); + this.sb.append("\n"); + this.sb.append(this.indent); + this.sb.append(") AS alias\n"); + this.sb.append(this.indent); + this.sb.append("WHERE "); + + if (selection.getMatchMode().isEquals()) { + if (selection.getMatchMode().isCaseSensitve()) { + this.sb.append("content = ?\n"); + } else { + this.sb.append("content ILIKE ?\n"); + } + } else { + value = "%" + value + "%"; + if (selection.getMatchMode().isCaseSensitve()) { + this.sb.append("content LIKE ?\n"); + } else { + this.sb.append("content ILIKE ?\n"); + } + } + + this.sb.append(this.indent); + this.sb.append(")\n"); + + this.values.add(value); + } + + @Override + public void visit(IntegerParameterSelection selection) { + xpath(selection.getBagKey(), selection.getParamKey(), selection.getValue().toString()); + } + + @Override + public void visit(BooleanParameterSelection selection) { + xpath(selection.getBagKey(), selection.getParamKey(), selection.getValue().toString()); + } + + @Override + public void visit(LongParameterSelection selection) { + xpath(selection.getBagKey(), selection.getParamKey(), selection.getValue().toString()); + } + + @Override + public void visit(FloatParameterSelection selection) { + xpath(selection.getBagKey(), selection.getParamKey(), selection.getValue().toString()); + } + + @Override + public void visit(DateParameterSelection selection) { + xpath(selection.getBagKey(), selection.getParamKey(), + ISO8601FormatFactory.getInstance().formatDate(selection.getValue())); + } + + @Override + public void visit(NullParameterSelection selection) { + String xpath = "cast(xpath('//Resource/ParameterBag[@Id=\"${bagKey}\"]/Parameter[@Id=\"${paramKey}\"]', asxml) as text[]) = '{}'\n"; + this.sb.append(this.indent); + xpath = xpath.replace("${bagKey}", selection.getBagKey()); + xpath = xpath.replace("${paramKey}", selection.getParamKey()); + this.sb.append(xpath); + } + + @Override + public void visit(ParameterBagSelection selection) { + String xpath = "cast(xpath('//Resource/ParameterBag[@Id=\"${bagKey}\"]', asxml) as text[]) != '{}'\n"; + this.sb.append(this.indent); + xpath = xpath.replace("${bagKey}", selection.getBagKey()); + this.sb.append(xpath); + } + + @Override + public void visit(NullParameterBagSelection selection) { + String xpath = "cast(xpath('//Resource/ParameterBag[@Id=\"${bagKey}\"]', asxml) as text[]) = '{}'\n"; + this.sb.append(this.indent); + xpath = xpath.replace("${bagKey}", selection.getBagKey()); + this.sb.append(xpath); + } + + @Override + public void visit(DateRangeParameterSelection selection) { + throw new UnsupportedOperationException("Not yet supported!"); + } + + @Override + public void visit(StringListParameterSelection selection) { + throw new UnsupportedOperationException("Not yet supported!"); + } + + /** + * @param ps + * @throws SQLException + */ + public void setValues(PreparedStatement ps) throws SQLException { + if (this.any) { + ps.setString(1, this.type); + return; + } + + ps.setString(1, this.type); + for (int i = 0; i < this.values.size(); i++) { + ps.setObject(i + 2, this.values.get(i)); + } + } +} diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceDao.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceDao.java new file mode 100644 index 000000000..4545b8a88 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceDao.java @@ -0,0 +1,178 @@ +/* + * 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.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.sax.SAXResult; + +import li.strolch.model.Resource; +import li.strolch.model.ResourceVisitor; +import li.strolch.model.Tags; +import li.strolch.model.query.ResourceQuery; +import li.strolch.model.xml.ResourceToSaxVisitor; +import li.strolch.model.xml.SimpleStrolchElementListener; +import li.strolch.model.xml.XmlModelSaxReader; +import li.strolch.persistence.api.ResourceDao; +import li.strolch.persistence.api.StrolchPersistenceException; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +@SuppressWarnings("nls") +public class PostgreSqlResourceDao extends PostgresqlDao implements ResourceDao { + + public static final String RESOURCES = "resources"; + + protected PostgreSqlResourceDao(PostgreSqlStrolchTransaction tx) { + super(tx); + } + + @Override + protected String getClassName() { + return Tags.RESOURCE; + } + + @Override + protected String getTableName() { + return RESOURCES; + } + + @Override + protected Resource parseFromXml(String id, String type, SQLXML sqlxml) { + SimpleStrolchElementListener listener = new SimpleStrolchElementListener(); + try (InputStream binaryStream = sqlxml.getBinaryStream()) { + SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); + parser.parse(binaryStream, new XmlModelSaxReader(listener)); + } catch (SQLException | IOException | SAXException | ParserConfigurationException e) { + throw new StrolchPersistenceException(MessageFormat.format( + "Failed to extract Resource from sqlxml value for {0} / {1}", id, type)); + } + + if (listener.getResources().size() == 0) + throw new StrolchPersistenceException(MessageFormat.format( + "No Resource parsed from sqlxml value for {0} / {1}", id, type)); + if (listener.getResources().size() > 1) + throw new StrolchPersistenceException(MessageFormat.format( + "Multiple Resources parsed from sqlxml value for {0} / {1}", id, type)); + + return listener.getResources().get(0); + } + + protected SQLXML createSqlXml(Resource res, PreparedStatement preparedStatement) throws SQLException, SAXException { + SQLXML sqlxml = this.tx.getConnection().createSQLXML(); + SAXResult saxResult = sqlxml.setResult(SAXResult.class); + ContentHandler contentHandler = saxResult.getHandler(); + contentHandler.startDocument(); + new ResourceToSaxVisitor(contentHandler).visit(res); + contentHandler.endDocument(); + return sqlxml; + } + + @Override + protected void internalSave(final Resource res) { + String sql = "insert into " + getTableName() + " (id, name, type, asxml) values (?, ?, ?, ?)"; + try (PreparedStatement preparedStatement = PostgreSqlResourceDao.this.tx.getConnection().prepareStatement(sql)) { + preparedStatement.setString(1, res.getId()); + preparedStatement.setString(2, res.getName()); + preparedStatement.setString(3, res.getType()); + + SQLXML sqlxml = createSqlXml(res, preparedStatement); + preparedStatement.setSQLXML(4, sqlxml); + try { + int modCount = preparedStatement.executeUpdate(); + if (modCount != 1) { + String msg = "Expected to save 1 element with id {0} but SQL statement modified {1} elements!"; + msg = MessageFormat.format(msg, res.getId(), modCount); + throw new StrolchPersistenceException(msg); + } + } finally { + sqlxml.free(); + } + + } catch (SQLException | SAXException e) { + throw new StrolchPersistenceException(MessageFormat.format("Failed to insert Resource {0} due to {1}", + res.getLocator(), e.getLocalizedMessage()), e); + } + } + + @Override + protected void internalUpdate(final Resource resource) { + String sql = "update " + getTableName() + " set name = ?, type = ?, asxml = ? where id = ? "; + try (PreparedStatement preparedStatement = PostgreSqlResourceDao.this.tx.getConnection().prepareStatement(sql)) { + + preparedStatement.setString(1, resource.getName()); + preparedStatement.setString(2, resource.getType()); + preparedStatement.setString(4, resource.getId()); + + SQLXML sqlxml = createSqlXml(resource, preparedStatement); + preparedStatement.setSQLXML(3, sqlxml); + try { + int modCount = preparedStatement.executeUpdate(); + if (modCount != 1) { + String msg = "Expected to update 1 element with id {0} but SQL statement modified {1} elements!"; + msg = MessageFormat.format(msg, resource.getId(), modCount); + throw new StrolchPersistenceException(msg); + } + } finally { + sqlxml.free(); + } + + } catch (SQLException | SAXException e) { + throw new StrolchPersistenceException(MessageFormat.format("Failed to update Resource {0} due to {1}", + resource.getLocator(), e.getLocalizedMessage()), e); + } + } + + @Override + public List doQuery(ResourceQuery query, ResourceVisitor resourceVisitor) { + + PostgreSqlResourceQueryVisitor queryVisitor = new PostgreSqlResourceQueryVisitor("id, asxml"); + query.accept(queryVisitor); + queryVisitor.validate(); + + List list = new ArrayList<>(); + + String sql = queryVisitor.getSql(); + try (PreparedStatement ps = PostgreSqlResourceDao.this.tx.getConnection().prepareStatement(sql)) { + queryVisitor.setValues(ps); + + try (ResultSet result = ps.executeQuery()) { + while (result.next()) { + String id = result.getString("id"); + SQLXML sqlxml = result.getSQLXML("asxml"); + Resource t = parseFromXml(id, queryVisitor.getType(), sqlxml); + list.add(resourceVisitor.visit(t)); + } + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to perform query due to: " + e.getMessage(), e); + } + + return list; + } +} diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceQueryVisitor.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceQueryVisitor.java new file mode 100644 index 000000000..4a07fc09f --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlResourceQueryVisitor.java @@ -0,0 +1,42 @@ +/* + * 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.Tags; +import li.strolch.model.query.ResourceQueryVisitor; + +/** + * @author Robert von Burg + */ +public class PostgreSqlResourceQueryVisitor extends PostgreSqlQueryVisitor implements ResourceQueryVisitor { + + /** + * @param fields + */ + public PostgreSqlResourceQueryVisitor(String fields) { + super(fields); + } + + @Override + protected String getClassName() { + return Tags.RESOURCE; + } + + @Override + protected String getTableName() { + return PostgreSqlResourceDao.RESOURCES; + } +} diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java new file mode 100644 index 000000000..9fa65c181 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java @@ -0,0 +1,118 @@ +/* + * 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 li.strolch.agent.api.StrolchRealm; +import li.strolch.persistence.api.AbstractTransaction; +import li.strolch.persistence.api.AuditDao; +import li.strolch.persistence.api.OrderDao; +import li.strolch.persistence.api.PersistenceHandler; +import li.strolch.persistence.api.ResourceDao; +import li.strolch.persistence.api.TransactionResult; +import li.strolch.runtime.privilege.PrivilegeHandler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.model.Certificate; + +public class PostgreSqlStrolchTransaction extends AbstractTransaction { + + private static final Logger logger = LoggerFactory.getLogger(PostgreSqlStrolchTransaction.class); + private PostgreSqlPersistenceHandler persistenceHandler; + + private PostgresqlDao orderDao; + private PostgresqlDao resourceDao; + private AuditDao auditDao; + private Connection connection; + + public PostgreSqlStrolchTransaction(PrivilegeHandler privilegeHandler, StrolchRealm realm, Certificate certificate, + String action, PostgreSqlPersistenceHandler persistenceHandler) { + super(privilegeHandler, realm, certificate, action); + this.persistenceHandler = persistenceHandler; + } + + @Override + protected void writeChanges(TransactionResult txResult) throws Exception { + + // first perform DAOs + if (this.orderDao != null) + this.orderDao.commit(txResult); + if (this.resourceDao != null) + this.resourceDao.commit(txResult); + + // don't commit the connection, this is done in postCommit when we close the connection + } + + @Override + protected void rollback(TransactionResult txResult) throws Exception { + if (this.connection != null) { + try { + this.connection.rollback(); + } finally { + try { + this.connection.close(); + } catch (Exception e) { + logger.error("Failed to close connection due to " + e.getMessage(), e); //$NON-NLS-1$ + } + } + } + } + + @Override + protected void commit() throws Exception { + if (this.connection != null) { + this.connection.commit(); + this.connection.close(); + } + } + + OrderDao getOrderDao() { + if (this.orderDao == null) + this.orderDao = new PostgreSqlOrderDao(this); + return (OrderDao) this.orderDao; + } + + ResourceDao getResourceDao() { + if (this.resourceDao == null) + this.resourceDao = new PostgreSqlResourceDao(this); + return (ResourceDao) this.resourceDao; + } + + /** + * @return + */ + public AuditDao getAuditDao() { + if (this.auditDao == null) + this.auditDao = new PostgreSqlAuditDao(this); + return this.auditDao; + } + + Connection getConnection() { + if (this.connection == null) { + this.connection = this.persistenceHandler.getConnection(getRealm().getRealm()); + } + return this.connection; + } + + @Override + public PersistenceHandler getPersistenceHandler() { + return this.persistenceHandler; + } + +} diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgresqlDao.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgresqlDao.java new file mode 100644 index 000000000..885847d4f --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgresqlDao.java @@ -0,0 +1,396 @@ +/* + * 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.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.text.MessageFormat; +import java.util.ArrayList; +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.StrolchPersistenceException; +import li.strolch.persistence.api.TransactionResult; + +@SuppressWarnings("nls") +public abstract class PostgresqlDao implements StrolchDao { + + protected PostgreSqlStrolchTransaction tx; + protected List commands; + + public PostgresqlDao(PostgreSqlStrolchTransaction tx) { + this.tx = tx; + this.commands = new ArrayList<>(); + } + + protected abstract String getClassName(); + + protected abstract String getTableName(); + + protected abstract T parseFromXml(String id, String type, SQLXML xml); + + @Override + public boolean hasElement(String type, String id) { + String sql = "select count(*) from " + getTableName() + " where type = ? and id = ?"; + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + statement.setString(1, type); + statement.setString(2, id); + try (ResultSet result = statement.executeQuery()) { + result.next(); + long numberOfElements = result.getLong(1); + if (numberOfElements == 0) + return false; + if (numberOfElements == 1) + return true; + + String msg = MessageFormat.format("Non unique number of elements with type {0} and id {1}", type, id); + throw new StrolchPersistenceException(msg); + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query size due to: " + e.getMessage(), e); + } + } + + @Override + public long querySize() { + String sql = "select count(*) from " + getTableName(); + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + try (ResultSet result = statement.executeQuery()) { + result.next(); + return result.getLong(1); + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query size due to: " + e.getMessage(), e); + } + } + + @Override + public long querySize(String type) { + String sql = "select count(*) from " + getTableName() + " where type = ?"; + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + statement.setString(1, type); + try (ResultSet result = statement.executeQuery()) { + result.next(); + return result.getLong(1); + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query size due to: " + e.getMessage(), e); + } + } + + @Override + public Set queryKeySet() { + + Set keySet = new HashSet<>(); + + String sql = "select id from " + getTableName(); + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + try (ResultSet result = statement.executeQuery()) { + while (result.next()) { + keySet.add(result.getString("id")); + } + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query key set due to: " + e.getMessage(), e); + } + + return keySet; + } + + @Override + public Set queryKeySet(String type) { + Set keySet = new HashSet<>(); + + String sql = "select id from " + getTableName() + " where type = ?"; + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + statement.setString(1, type); + try (ResultSet result = statement.executeQuery()) { + while (result.next()) { + keySet.add(result.getString("id")); + } + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query key set due to: " + e.getMessage(), e); + } + + return keySet; + } + + @Override + public Set queryTypes() { + Set keySet = new HashSet<>(); + + String sql = "select distinct type from " + getTableName(); + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + try (ResultSet result = statement.executeQuery()) { + while (result.next()) { + keySet.add(result.getString("type")); + } + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query types due to: " + e.getMessage(), e); + } + + return keySet; + } + + @Override + public T queryBy(String type, String id) { + + String sql = "select id, name, type, asxml from " + getTableName() + " where id = ? and type = ?"; + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + statement.setString(1, id); + statement.setString(2, type); + try (ResultSet result = statement.executeQuery()) { + if (!result.next()) { + return null; + } + + SQLXML sqlxml = result.getSQLXML("asxml"); + T t = parseFromXml(id, type, sqlxml); + if (result.next()) + throw new StrolchPersistenceException("Non unique result for query: " + sql); + return t; + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query types due to: " + e.getMessage(), e); + } + } + + @Override + public List queryAll() { + + List list = new ArrayList<>(); + String sql = "select id, name, type, asxml from " + getTableName(); + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + try (ResultSet result = statement.executeQuery()) { + while (result.next()) { + String id = result.getString("id"); + String type = result.getString("type"); + SQLXML sqlxml = result.getSQLXML("asxml"); + T t = parseFromXml(id, type, sqlxml); + list.add(t); + } + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query types due to: " + e.getMessage(), e); + } + + return list; + } + + @Override + public List queryAll(String type) { + + List list = new ArrayList<>(); + String sql = "select id, name, type, asxml from " + getTableName() + " where type = ?"; + try (PreparedStatement statement = this.tx.getConnection().prepareStatement(sql)) { + statement.setString(1, type); + try (ResultSet result = statement.executeQuery()) { + while (result.next()) { + String id = result.getString("id"); + SQLXML sqlxml = result.getSQLXML("asxml"); + T t = parseFromXml(id, type, sqlxml); + list.add(t); + } + } + } catch (SQLException e) { + throw new StrolchPersistenceException("Failed to query types due to: " + e.getMessage(), e); + } + + return list; + } + + @Override + public void save(final T res) { + this.commands.add(new DaoCommand() { + @Override + public void doComand(TransactionResult txResult) { + internalSave(res); + txResult.incCreated(1); + } + }); + } + + @Override + public void saveAll(final List elements) { + this.commands.add(new DaoCommand() { + @Override + public void doComand(TransactionResult txResult) { + for (T element : elements) { + internalSave(element); + } + txResult.incCreated(elements.size()); + } + }); + } + + @Override + public void update(final T element) { + this.commands.add(new DaoCommand() { + @Override + public void doComand(TransactionResult txResult) { + internalUpdate(element); + txResult.incUpdated(1); + } + }); + } + + @Override + public void updateAll(final List elements) { + this.commands.add(new DaoCommand() { + @Override + public void doComand(TransactionResult txResult) { + for (T element : elements) { + internalUpdate(element); + } + txResult.incUpdated(elements.size()); + } + }); + } + + @Override + public void remove(final T element) { + this.commands.add(new DaoCommand() { + @Override + public void doComand(TransactionResult txResult) { + internalRemove(element); + txResult.incDeleted(1); + } + }); + } + + @Override + public void removeAll(final List elements) { + this.commands.add(new DaoCommand() { + @Override + public void doComand(TransactionResult txResult) { + for (T element : elements) { + internalRemove(element); + } + txResult.incDeleted(elements.size()); + } + }); + } + + @Override + public long removeAll() { + + final long toRemove = querySize(); + + this.commands.add(new DaoCommand() { + @Override + public void doComand(TransactionResult txResult) { + internalRemoveAll(toRemove); + txResult.incDeleted(toRemove); + } + }); + + return toRemove; + } + + @Override + public long removeAllBy(final String type) { + + final long toRemove = querySize(type); + + this.commands.add(new DaoCommand() { + @Override + public void doComand(TransactionResult txResult) { + internalRemoveAllBy(toRemove, type); + txResult.incDeleted(toRemove); + } + }); + + return toRemove; + } + + /** + * @param element + */ + protected abstract void internalSave(T element); + + /** + * @param element + */ + protected abstract void internalUpdate(T element); + + protected void internalRemove(final T element) { + String sql = "delete from " + getTableName() + " where id = ?"; + try (PreparedStatement preparedStatement = this.tx.getConnection().prepareStatement(sql)) { + + preparedStatement.setString(1, element.getId()); + int modCount = preparedStatement.executeUpdate(); + if (modCount != 1) { + String msg = "Expected to delete 1 element with id {0} but SQL statement modified {1} elements!"; + msg = MessageFormat.format(msg, element.getId(), modCount); + throw new StrolchPersistenceException(msg); + } + + } catch (SQLException e) { + throw new StrolchPersistenceException(MessageFormat.format("Failed to remove {0} due to {2}", + element.getLocator(), e.getLocalizedMessage()), e); + } + } + + protected void internalRemoveAll(final long toRemove) { + String sql = "delete from " + getTableName(); + try (PreparedStatement preparedStatement = this.tx.getConnection().prepareStatement(sql)) { + int modCount = preparedStatement.executeUpdate(); + if (modCount != toRemove) { + String msg = "Expected to delete {0} elements but SQL statement removed {1} elements!"; + msg = MessageFormat.format(msg, toRemove, modCount); + throw new StrolchPersistenceException(msg); + } + + } catch (SQLException e) { + throw new StrolchPersistenceException(MessageFormat.format("Failed to remove all elements due to {0}", + e.getLocalizedMessage()), e); + } + } + + protected void internalRemoveAllBy(final long toRemove, String type) { + String sql = "delete from " + getTableName() + " where type = ?"; + try (PreparedStatement preparedStatement = this.tx.getConnection().prepareStatement(sql)) { + preparedStatement.setString(1, type); + int modCount = preparedStatement.executeUpdate(); + if (modCount != toRemove) { + String msg = "Expected to delete {0} elements of type {1} but SQL statement removed {2} elements!"; + msg = MessageFormat.format(msg, toRemove, type, modCount); + throw new StrolchPersistenceException(msg); + } + + } catch (SQLException e) { + throw new StrolchPersistenceException(MessageFormat.format( + "Failed to remove all elements of type {0} due to {1}", type, e.getLocalizedMessage()), e); + } + } + + void commit(TransactionResult txResult) { + for (DaoCommand command : this.commands) { + command.doComand(txResult); + } + } + + void rollback() { + this.commands.clear(); + } +} diff --git a/li.strolch.persistence.postgresql/src/main/resources/componentVersion.properties b/li.strolch.persistence.postgresql/src/main/resources/componentVersion.properties new file mode 100644 index 000000000..1f050160f --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/resources/componentVersion.properties @@ -0,0 +1,6 @@ +groupId=${project.groupId} +artifactId=${project.artifactId} +artifactVersion=${project.version} +scmRevision=r${buildNumber} +scmBranch=${scmBranch} +buildTimestamp=${buildTimestamp} \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/main/resources/db_schema_0.1.0_drop.sql b/li.strolch.persistence.postgresql/src/main/resources/db_schema_0.1.0_drop.sql new file mode 100644 index 000000000..fe35890fd --- /dev/null +++ b/li.strolch.persistence.postgresql/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/li.strolch.persistence.postgresql/src/main/resources/db_schema_0.1.0_initial.sql b/li.strolch.persistence.postgresql/src/main/resources/db_schema_0.1.0_initial.sql new file mode 100644 index 000000000..808f6b97a --- /dev/null +++ b/li.strolch.persistence.postgresql/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/li.strolch.persistence.postgresql/src/main/resources/db_schema_0.2.0_drop.sql b/li.strolch.persistence.postgresql/src/main/resources/db_schema_0.2.0_drop.sql new file mode 100644 index 000000000..467a2c3ed --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/resources/db_schema_0.2.0_drop.sql @@ -0,0 +1,8 @@ + +DROP TABLE IF EXISTS resources; +DROP TABLE IF EXISTS orders; +DROP TABLE IF EXISTS audits; +DROP TABLE IF EXISTS db_version; + +DROP TYPE IF EXISTS order_state; +DROP TYPE IF EXISTS access_type; diff --git a/li.strolch.persistence.postgresql/src/main/resources/db_schema_0.2.0_initial.sql b/li.strolch.persistence.postgresql/src/main/resources/db_schema_0.2.0_initial.sql new file mode 100644 index 000000000..dd7a298be --- /dev/null +++ b/li.strolch.persistence.postgresql/src/main/resources/db_schema_0.2.0_initial.sql @@ -0,0 +1,61 @@ + +-- DB_VERSION +CREATE TABLE IF NOT EXISTS db_version ( + id SERIAL PRIMARY KEY, + version varchar(255), + description varchar(255), + created timestamp with time zone +); + +-- RESOURCES +CREATE TABLE IF NOT EXISTS resources ( + id varchar(255) PRIMARY KEY, + name VARCHAR(255), + type VARCHAR(255), + asxml xml +); + +-- ORDERS +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 +); + +-- AUDITS +CREATE TYPE access_type AS ENUM ('READ', 'CREATE', 'UPDATE', 'DELETE'); +CREATE TABLE IF NOT EXISTS audits ( + id bigint PRIMARY KEY, + username VARCHAR(255) NOT NULL, + firstname VARCHAR(255) NOT NULL, + lastname VARCHAR(255) NOT NULL, + date timestamp with time zone NOT NULL, + + element_type VARCHAR(255) NOT NULL, + element_accessed VARCHAR(255) NOT NULL, + new_version timestamp with time zone, + + action VARCHAR(255) NOT NULL, + access_type access_type NOT NULL +); + +-- set version +INSERT INTO db_version + (version, description, created) +values( + '0.1.0', + 'Initial schema version', + CURRENT_TIMESTAMP +); +INSERT INTO db_version + (version, description, created) +values( + '0.2.0', + 'Added new table for audits', + CURRENT_TIMESTAMP +); diff --git a/li.strolch.persistence.postgresql/src/main/resources/db_version.properties b/li.strolch.persistence.postgresql/src/main/resources/db_version.properties new file mode 100644 index 000000000..ac24bcc3c --- /dev/null +++ b/li.strolch.persistence.postgresql/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.2.0 \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/AuditQueryTest.java b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/AuditQueryTest.java new file mode 100644 index 000000000..a85cc67f0 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/AuditQueryTest.java @@ -0,0 +1,270 @@ +/* + * 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.persistence.postgresql.dao.test.CachedDaoTest.CONFIG_SRC; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_PASSWORD; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_STORE_PATH_DIR; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_URL; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_USERNAME; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.RUNTIME_PATH; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.dropSchema; +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; +import java.util.List; + +import li.strolch.agent.api.AuditTrail; +import li.strolch.agent.api.StrolchRealm; +import li.strolch.model.ModelGenerator; +import li.strolch.model.Tags; +import li.strolch.model.audit.AccessType; +import li.strolch.model.audit.Audit; +import li.strolch.model.audit.AuditQuery; +import li.strolch.persistence.api.AbstractTransaction; +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.persistence.postgresql.PostgreSqlAuditQueryVisitor; +import li.strolch.runtime.StrolchConstants; +import li.strolch.testbase.runtime.RuntimeMock; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.utils.StringMatchMode; +import ch.eitchnet.utils.collections.DateRange; + +/** + * @author Robert von Burg + */ +public class AuditQueryTest { + + private static final Logger logger = LoggerFactory.getLogger(AuditQueryTest.class); + private static RuntimeMock runtimeMock; + + private static Date past; + private static Date earlier; + private static Date current; + private static Date later; + private static Date future; + + @BeforeClass + public static void beforeClass() throws SQLException { + + dropSchema(DB_URL, DB_USERNAME, DB_PASSWORD); + + File rootPath = new File(RUNTIME_PATH); + File configSrc = new File(CONFIG_SRC); + runtimeMock = new RuntimeMock(); + runtimeMock.mockRuntime(rootPath, configSrc); + new File(rootPath, DB_STORE_PATH_DIR).mkdir(); + runtimeMock.startContainer(); + + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.set(2000, 1, 1); + past = cal.getTime(); + cal.set(2000, 4, 1); + earlier = cal.getTime(); + cal.set(2000, 6, 1); + current = cal.getTime(); + cal.set(2000, 8, 1); + later = cal.getTime(); + cal.set(2000, 11, 1); + future = cal.getTime(); + + Certificate cert = runtimeMock.getPrivilegeHandler().authenticate("test", "test".getBytes()); + StrolchRealm realm = runtimeMock.getRealm(StrolchConstants.DEFAULT_REALM); + int i = 0; + try (StrolchTransaction tx = realm.openTx(cert, "test")) { + ((AbstractTransaction) tx).setSuppressAudits(true); + AuditTrail auditTrail = tx.getAuditTrail(); + + Audit randomAudit; + randomAudit = ModelGenerator.randomAudit(); + randomAudit.setId(i++); + randomAudit.setUsername("earlier"); + randomAudit.setDate(earlier); + randomAudit.setAccessType(AccessType.CREATE); + randomAudit.setAction("create"); + randomAudit.setElementAccessed(randomAudit.getAccessType().name()); + auditTrail.add(tx, randomAudit); + + randomAudit = ModelGenerator.randomAudit(); + randomAudit.setId(i++); + randomAudit.setDate(current); + randomAudit.setUsername("current"); + randomAudit.setAccessType(AccessType.READ); + randomAudit.setAction("read"); + randomAudit.setElementAccessed(randomAudit.getAccessType().name()); + auditTrail.add(tx, randomAudit); + + randomAudit = ModelGenerator.randomAudit(); + randomAudit.setId(i++); + randomAudit.setDate(later); + randomAudit.setUsername("later"); + randomAudit.setAccessType(AccessType.UPDATE); + randomAudit.setAction("update"); + randomAudit.setElementAccessed(randomAudit.getAccessType().name()); + auditTrail.add(tx, randomAudit); + + randomAudit = ModelGenerator.randomAudit(); + randomAudit.setId(i++); + randomAudit.setDate(current); + randomAudit.setUsername("current"); + randomAudit.setAccessType(AccessType.DELETE); + randomAudit.setAction("delete"); + randomAudit.setElementAccessed(randomAudit.getAccessType().name()); + auditTrail.add(tx, randomAudit); + + randomAudit = ModelGenerator.randomAudit(); + randomAudit.setId(i++); + randomAudit.setDate(current); + randomAudit.setUsername("current"); + randomAudit.setAccessType(AccessType.CREATE); + randomAudit.setAction("create"); + randomAudit.setElementAccessed(randomAudit.getAccessType().name()); + auditTrail.add(tx, randomAudit); + } + } + + @AfterClass + public static void afterClass() { + runtimeMock.destroyRuntime(); + } + + public Connection openConn() throws SQLException { + String url = "jdbc:postgresql://localhost/testdb"; + String username = "testuser"; + String password = "test"; + Connection connection = DriverManager.getConnection(url, username, password); + connection.setAutoCommit(false); + return connection; + } + + @Test + public void shouldQueryTypeAndDateRange() throws SQLException { + AuditQuery query = new AuditQuery(Tags.AUDIT, new DateRange().from(earlier, true).to(later, true)); + performQuery(query, Arrays.asList("0", "1", "2", "3", "4")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(current, true).to(current, true)); + performQuery(query, Arrays.asList("1", "3", "4")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(current, true)); + performQuery(query, Arrays.asList("1", "2", "3", "4")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().to(current, true)); + performQuery(query, Arrays.asList("0", "1", "3", "4")); + + query = new AuditQuery(Tags.RESOURCE, new DateRange().from(past, true).to(future, true)); + performQuery(query, Arrays. asList()); + } + + @Test + public void shouldQueryAudits() throws SQLException { + AuditQuery query = new AuditQuery(Tags.AUDIT, new DateRange().from(past, true).to(future, true)); + query.action().accessTypes(AccessType.CREATE, AccessType.READ); + performQuery(query, Arrays.asList("0", "1", "4")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(past, true).to(future, true)); + query.action().accessTypes(AccessType.CREATE); + performQuery(query, Arrays.asList("0", "4")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(past, true).to(future, true)); + query.action().accessTypes(AccessType.CREATE, AccessType.READ) + .actions(StringMatchMode.EQUALS_CASE_SENSITIVE, "create", "read"); + performQuery(query, Arrays.asList("0", "1", "4")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(past, true).to(future, true)); + query.action().accessTypes(AccessType.CREATE, AccessType.READ) + .actions(StringMatchMode.EQUALS_CASE_SENSITIVE, "read"); + performQuery(query, Arrays.asList("1")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(past, true).to(future, true)); + query.element().elementsAccessed(StringMatchMode.CONTAINS_CASE_INSENSITIVE, "crea"); + performQuery(query, Arrays.asList("0", "4")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(past, true).to(future, true)); + query.element().elementsAccessed(StringMatchMode.CONTAINS_CASE_SENSITIVE, "crea"); + performQuery(query, Arrays. asList()); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(past, true).to(future, true)); + query.element().elementsAccessed(StringMatchMode.EQUALS_CASE_INSENSITIVE, "create"); + performQuery(query, Arrays.asList("0", "4")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(past, true).to(future, true)); + query.identity().usernames(StringMatchMode.EQUALS_CASE_INSENSITIVE, "earlier"); + performQuery(query, Arrays.asList("0")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(past, true).to(future, true)); + query.identity().usernames(StringMatchMode.EQUALS_CASE_INSENSITIVE, "earlier", "later"); + performQuery(query, Arrays.asList("0", "2")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(past, true).to(future, true)); + query.identity().usernames(StringMatchMode.EQUALS_CASE_INSENSITIVE, "earlier") + .firstnames(StringMatchMode.CONTAINS_CASE_INSENSITIVE, "enn"); + performQuery(query, Arrays.asList("0")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(past, true).to(future, true)); + query.identity().usernames(StringMatchMode.EQUALS_CASE_INSENSITIVE, "earlier") + .firstnames(StringMatchMode.CONTAINS_CASE_INSENSITIVE, "enn") + .lastnames(StringMatchMode.CONTAINS_CASE_INSENSITIVE, "kennedy"); + performQuery(query, Arrays.asList("0")); + + query = new AuditQuery(Tags.AUDIT, new DateRange().from(past, true).to(future, true)); + query.identity().firstnames(StringMatchMode.CONTAINS_CASE_INSENSITIVE, "enn") + .lastnames(StringMatchMode.CONTAINS_CASE_INSENSITIVE, "kennedy"); + performQuery(query, Arrays.asList("0", "1", "2", "3", "4")); + } + + private void performQuery(AuditQuery query, List expected) throws SQLException { + PostgreSqlAuditQueryVisitor visitor = new PostgreSqlAuditQueryVisitor("id"); + query.accept(visitor); + List ids = queryIds(visitor); + assertEquals(new HashSet<>(expected), new HashSet<>(ids)); + } + + private List queryIds(PostgreSqlAuditQueryVisitor visitor) throws SQLException { + String sql = visitor.getSql(); + logger.info("\n" + sql); + List ids = new ArrayList<>(); + try (Connection con = openConn()) { + try (PreparedStatement ps = con.prepareStatement(sql)) { + visitor.setValues(ps); + + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + ids.add(rs.getString(1)); + } + } + } + + return ids; + } +} diff --git a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/CachedDaoTest.java b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/CachedDaoTest.java new file mode 100644 index 000000000..902e86e26 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/CachedDaoTest.java @@ -0,0 +1,81 @@ +/* + * 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 java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.text.MessageFormat; + +import li.strolch.persistence.postgresql.DbSchemaVersionCheck; +import li.strolch.testbase.runtime.AbstractModelTest; +import li.strolch.testbase.runtime.RuntimeMock; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.StringHelper; + +public class CachedDaoTest extends AbstractModelTest { + + public static final String RUNTIME_PATH = "target/cachedStrolchRuntime/"; //$NON-NLS-1$ + public static final String DB_STORE_PATH_DIR = "dbStore"; //$NON-NLS-1$ + public static final String CONFIG_SRC = "src/test/resources/cachedruntime"; //$NON-NLS-1$ + + public static final String DB_URL = "jdbc:postgresql://localhost/testdb"; //$NON-NLS-1$ + public static final String DB_USERNAME = "testuser"; //$NON-NLS-1$ + public static final String DB_PASSWORD = "test"; //$NON-NLS-1$ + + private static final Logger logger = LoggerFactory.getLogger(CachedDaoTest.class); + + protected static RuntimeMock runtimeMock; + + @Override + protected RuntimeMock getRuntimeMock() { + return runtimeMock; + } + + @BeforeClass + public static void beforeClass() throws SQLException { + + dropSchema(DB_URL, DB_USERNAME, DB_PASSWORD); + + File rootPath = new File(RUNTIME_PATH); + File configSrc = new File(CONFIG_SRC); + runtimeMock = new RuntimeMock(); + runtimeMock.mockRuntime(rootPath, configSrc); + new File(rootPath, DB_STORE_PATH_DIR).mkdir(); + runtimeMock.startContainer(); + } + + public static void dropSchema(String dbUrl, String dbUsername, String dbPassword) throws SQLException { + String dbVersion = DbSchemaVersionCheck.getExpectedDbVersion(); + logger.info(MessageFormat.format("Dropping schema for expected version {0}", dbVersion)); + String sql = DbSchemaVersionCheck.getSql(dbVersion, "drop"); //$NON-NLS-1$ + logger.info(StringHelper.NEW_LINE + sql); + try (Connection connection = DriverManager.getConnection(dbUrl, dbUsername, dbPassword)) { + connection.prepareStatement(sql).execute(); + } + } + + @AfterClass + public static void afterClass() { + runtimeMock.destroyRuntime(); + } +} diff --git a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/ObserverUpdateTest.java b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/ObserverUpdateTest.java new file mode 100644 index 000000000..bb1279902 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/ObserverUpdateTest.java @@ -0,0 +1,142 @@ +/* + * 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 li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_PASSWORD; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_URL; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_USERNAME; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.dropSchema; +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.sql.SQLException; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import li.strolch.agent.api.Observer; +import li.strolch.agent.api.StrolchRealm; +import li.strolch.model.Order; +import li.strolch.model.Resource; +import li.strolch.model.State; +import li.strolch.model.StrolchRootElement; +import li.strolch.model.Tags; +import li.strolch.persistence.api.ModificationResult; +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.runtime.StrolchConstants; +import li.strolch.runtime.privilege.PrivilegeHandler; +import li.strolch.testbase.runtime.RuntimeMock; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author Robert von Burg + */ +public class ObserverUpdateTest { + + public static final String RUNTIME_PATH = "target/observerUpdateStrolchRuntime/"; //$NON-NLS-1$ + public static final String DB_STORE_PATH_DIR = "dbStore"; //$NON-NLS-1$ + public static final String CONFIG_SRC = "src/test/resources/cachedruntime"; //$NON-NLS-1$ + + protected static RuntimeMock runtimeMock; + + protected RuntimeMock getRuntimeMock() { + return runtimeMock; + } + + @BeforeClass + public static void beforeClass() throws SQLException { + + dropSchema(DB_URL, DB_USERNAME, DB_PASSWORD); + + File rootPath = new File(RUNTIME_PATH); + File configSrc = new File(CONFIG_SRC); + runtimeMock = new RuntimeMock(); + runtimeMock.mockRuntime(rootPath, configSrc); + new File(rootPath, DB_STORE_PATH_DIR).mkdir(); + runtimeMock.startContainer(); + } + + @AfterClass + public static void afterClass() { + runtimeMock.destroyRuntime(); + } + + 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(); + StrolchRealm realm = runtimeMock.getRealm(StrolchConstants.DEFAULT_REALM); + realm.getObserverHandler().registerObserver(Tags.ORDER, observer); + realm.getObserverHandler().registerObserver(Tags.RESOURCE, observer); + + PrivilegeHandler privilegeHandler = runtimeMock.getAgent().getContainer().getPrivilegeHandler(); + Certificate certificate = privilegeHandler.authenticate("test", "test".getBytes()); //$NON-NLS-1$ //$NON-NLS-2$ + + // 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 = realm.openTx(certificate, "test")) { //$NON-NLS-1$ + tx.getOrderMap().add(tx, newOrder); + } + + // create resource + Resource newResource = createResource("MyTestResource", "Test Name", "TestType"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + try (StrolchTransaction tx = realm.openTx(certificate, "test");) { //$NON-NLS-1$ + tx.getResourceMap().add(tx, newResource); + } + + assertEquals(2, observer.results.size()); + assertEquals(1, observer.results.get(Tags.ORDER).getCreated().size()); + assertEquals(1, observer.results.get(Tags.RESOURCE).getCreated().size()); + } +} diff --git a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/QueryTest.java b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/QueryTest.java new file mode 100644 index 000000000..328d00f36 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/QueryTest.java @@ -0,0 +1,390 @@ +/* + * 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.persistence.postgresql.dao.test.CachedDaoTest.CONFIG_SRC; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_PASSWORD; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_STORE_PATH_DIR; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_URL; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_USERNAME; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.RUNTIME_PATH; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.dropSchema; +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; +import java.util.List; + +import li.strolch.agent.api.OrderMap; +import li.strolch.agent.api.ResourceMap; +import li.strolch.agent.api.StrolchRealm; +import li.strolch.model.ModelGenerator; +import li.strolch.model.State; +import li.strolch.model.query.DateSelection; +import li.strolch.model.query.IdSelection; +import li.strolch.model.query.NameSelection; +import li.strolch.model.query.OrSelection; +import li.strolch.model.query.OrderQuery; +import li.strolch.model.query.ParameterBagSelection; +import li.strolch.model.query.ParameterBagSelection.NullParameterBagSelection; +import li.strolch.model.query.ParameterSelection; +import li.strolch.model.query.ResourceQuery; +import li.strolch.model.query.StateSelection; +import li.strolch.model.query.StrolchTypeNavigation; +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.persistence.postgresql.PostgreSqlOrderQueryVisitor; +import li.strolch.persistence.postgresql.PostgreSqlQueryVisitor; +import li.strolch.persistence.postgresql.PostgreSqlResourceQueryVisitor; +import li.strolch.runtime.StrolchConstants; +import li.strolch.testbase.runtime.RuntimeMock; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.utils.StringMatchMode; + +/** + * @author Robert von Burg + */ +public class QueryTest { + + private static final Logger logger = LoggerFactory.getLogger(QueryTest.class); + private static RuntimeMock runtimeMock; + + private static Date past; + private static Date earlier; + private static Date current; + private static Date later; + private static Date future; + + @BeforeClass + public static void beforeClass() throws SQLException { + + dropSchema(DB_URL, DB_USERNAME, DB_PASSWORD); + + File rootPath = new File(RUNTIME_PATH); + File configSrc = new File(CONFIG_SRC); + runtimeMock = new RuntimeMock(); + runtimeMock.mockRuntime(rootPath, configSrc); + new File(rootPath, DB_STORE_PATH_DIR).mkdir(); + runtimeMock.startContainer(); + + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.set(2000, 1, 1); + past = cal.getTime(); + cal.set(2000, 4, 1); + earlier = cal.getTime(); + cal.set(2000, 6, 1); + current = cal.getTime(); + cal.set(2000, 8, 1); + later = cal.getTime(); + cal.set(2000, 11, 1); + future = cal.getTime(); + + Certificate cert = runtimeMock.getPrivilegeHandler().authenticate("test", "test".getBytes()); + StrolchRealm realm = runtimeMock.getRealm(StrolchConstants.DEFAULT_REALM); + try (StrolchTransaction tx = realm.openTx(cert, "test")) { + OrderMap orderMap = tx.getOrderMap(); + + orderMap.add(tx, ModelGenerator.createOrder("@1", "Order 1", "MyType1", earlier, State.CREATED)); + orderMap.add(tx, ModelGenerator.createOrder("@2", "Order 2", "MyType1", current, State.OPEN)); + orderMap.add(tx, ModelGenerator.createOrder("@3", "Order 3", "MyType1", later, State.CLOSED)); + orderMap.add(tx, ModelGenerator.createOrder("@4", "Order 4", "MyType2", earlier, State.CREATED)); + orderMap.add(tx, ModelGenerator.createOrder("@5", "Order 5", "MyType2", current, State.OPEN)); + orderMap.add(tx, ModelGenerator.createOrder("@6", "Order 6", "MyType2", later, State.CLOSED)); + + ResourceMap resourceMap = tx.getResourceMap(); + resourceMap.add(tx, ModelGenerator.createResource("@1", "Resource 1", "MyType1")); + resourceMap.add(tx, ModelGenerator.createResource("@2", "Resource 2", "MyType1")); + resourceMap.add(tx, ModelGenerator.createResource("@3", "Resource 3", "MyType1")); + resourceMap.add(tx, ModelGenerator.createResource("@4", "Resource 4", "MyType2")); + resourceMap.add(tx, ModelGenerator.createResource("@5", "Resource 5", "MyType2")); + resourceMap.add(tx, ModelGenerator.createResource("@6", "Resource 6", "MyType2")); + } + } + + @AfterClass + public static void afterClass() { + runtimeMock.destroyRuntime(); + } + + public Connection openConn() throws SQLException { + String url = "jdbc:postgresql://localhost/testdb"; + String username = "testuser"; + String password = "test"; + Connection connection = DriverManager.getConnection(url, username, password); + connection.setAutoCommit(false); + return connection; + } + + @Test + public void shouldQueryOrderAll() throws SQLException { + + OrderQuery query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.withAny(); + performOrderQuery(query, Arrays.asList("@1", "@2", "@3")); + } + + @Test + public void shouldQueryResourceAll() throws SQLException { + + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType2")); + query.withAny(); + performResourceQuery(query, Arrays.asList("@4", "@5", "@6")); + } + + @Test + public void shouldQueryOrderByDate() throws SQLException { + + // range + OrderQuery query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new DateSelection().from(earlier, false).to(later, false)); + performOrderQuery(query, Arrays.asList("@1", "@2", "@3")); + + // equals current + query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new DateSelection().from(current, false).to(current, false)); + performOrderQuery(query, Arrays.asList("@2")); + + // equals later + query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new DateSelection().from(later, false).to(later, false)); + performOrderQuery(query, Arrays. asList("@3")); + + // equals earlier + query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new DateSelection().from(earlier, false).to(earlier, false)); + performOrderQuery(query, Arrays. asList("@1")); + + // past + query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new DateSelection().to(past, false)); + performOrderQuery(query, Arrays. asList()); + + // future + query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new DateSelection().from(future, false)); + performOrderQuery(query, Arrays. asList()); + + // earlier + query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new DateSelection().from(past, false).to(earlier, true)); + performOrderQuery(query, Arrays. asList("@1")); + + // later + query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new DateSelection().from(later, false).to(future, true)); + performOrderQuery(query, Arrays. asList("@3")); + } + + @Test + public void shouldQueryOrderByState() throws SQLException { + + OrderQuery query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new StateSelection(State.CREATED)); + performOrderQuery(query, Arrays.asList("@1")); + + query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new StateSelection(State.OPEN)); + performOrderQuery(query, Arrays. asList("@2")); + } + + @Test + public void shouldQueryOrder1() throws SQLException { + + OrderQuery query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new IdSelection("@1", "@2"), + new NameSelection("Order 1", StringMatchMode.EQUALS_CASE_SENSITIVE)); + performOrderQuery(query, Arrays.asList("@1")); + } + + @Test + public void shouldQueryOrder2() throws SQLException { + + OrderQuery query = new OrderQuery(new StrolchTypeNavigation("MyType1")); + query.or().with(new IdSelection("@1", "@2"), + new NameSelection("order 1", StringMatchMode.EQUALS_CASE_SENSITIVE)); + performOrderQuery(query, Arrays.asList("@1", "@2")); + } + + @Test + public void shouldQueryResource1() throws SQLException { + + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.or().with(new IdSelection("@1", "@2"), + new NameSelection("Resource 1", StringMatchMode.EQUALS_CASE_SENSITIVE)); + performResourceQuery(query, Arrays.asList("@1", "@2")); + } + + @Test + public void shouldQueryResource2() throws SQLException { + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with( + new OrSelection(new IdSelection("@1"), new IdSelection("@2")), + new OrSelection(new NameSelection("Resource 1", StringMatchMode.EQUALS_CASE_SENSITIVE), + new NameSelection("Resource 2", StringMatchMode.EQUALS_CASE_SENSITIVE))); + performResourceQuery(query, Arrays.asList("@1", "@2")); + } + + @Test + public void shouldQueryResourceByBooleParam() throws SQLException { + + // select id, name, type, asxml + // from + // resources + // where + // type = 'MyType1' and + // ( + // cast(xpath('//Resource/ParameterBag/Parameter[@Id="@param1" and @Value="true"]', asxml) as text[]) != '{}' + // ) + + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(ParameterSelection.booleanSelection("@bag01", "@param1", true)); + performResourceQuery(query, Arrays.asList("@1", "@2", "@3")); + } + + @Test + public void shouldQueryResourceByFloagParam() throws SQLException { + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(ParameterSelection.floatSelection("@bag01", "@param2", 44.3)); + performResourceQuery(query, Arrays.asList("@1", "@2", "@3")); + } + + @Test + public void shouldQueryResourceByIntegerParam() throws SQLException { + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(ParameterSelection.integerSelection("@bag01", "@param3", 77)); + performResourceQuery(query, Arrays.asList("@1", "@2", "@3")); + } + + @Test + public void shouldQueryResourceByLongParam() throws SQLException { + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType2")); + query.and().with(ParameterSelection.longSelection("@bag01", "@param4", 4453234566L)); + performResourceQuery(query, Arrays.asList("@4", "@5", "@6")); + } + + @Test + public void shouldQueryResourceByStringParam() throws SQLException { + + List expected = Arrays.asList("@1", "@2", "@3"); + + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with( + ParameterSelection.stringSelection("@bag01", "@param5", "Strolch", + StringMatchMode.EQUALS_CASE_SENSITIVE)); + performResourceQuery(query, expected); + + query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with( + ParameterSelection.stringSelection("@bag01", "@param5", "strolch", + StringMatchMode.EQUALS_CASE_SENSITIVE)); + performResourceQuery(query, Arrays. asList()); + + query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with( + ParameterSelection.stringSelection("@bag01", "@param5", "strolch", + StringMatchMode.EQUALS_CASE_INSENSITIVE)); + performResourceQuery(query, expected); + + query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with( + ParameterSelection.stringSelection("@bag01", "@param5", "olch", + StringMatchMode.CONTAINS_CASE_INSENSITIVE)); + performResourceQuery(query, expected); + } + + @Test + public void shouldQueryResourceByDateParam() throws SQLException { + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(ParameterSelection.dateSelection("@bag01", "@param6", new Date(1354295525628L))); + performResourceQuery(query, Arrays.asList("@1", "@2", "@3")); + } + + @Test + public void shouldQueryResourceByNullParam1() throws SQLException { + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(ParameterSelection.nullSelection("@bag01", "@param6")); + performResourceQuery(query, Arrays. asList()); + } + + @Test + public void shouldQueryResourceByNullParam2() throws SQLException { + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(ParameterSelection.nullSelection("@bag01", "@param")); + performResourceQuery(query, Arrays.asList("@1", "@2", "@3")); + } + + @Test + public void shouldQueryResourceByBag() throws SQLException { + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new ParameterBagSelection("@bag01")); + performResourceQuery(query, Arrays.asList("@1", "@2", "@3")); + } + + @Test + public void shouldQueryResourceByNullBag() throws SQLException { + ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType1")); + query.and().with(new NullParameterBagSelection("@bag01")); + performResourceQuery(query, Arrays. asList()); + } + + private void performOrderQuery(OrderQuery query, List expected) throws SQLException { + PostgreSqlOrderQueryVisitor visitor = new PostgreSqlOrderQueryVisitor("id"); + query.accept(visitor); + List ids = queryIds(visitor); + assertEquals(new HashSet<>(expected), new HashSet<>(ids)); + } + + private void performResourceQuery(ResourceQuery query, List expected) throws SQLException { + PostgreSqlResourceQueryVisitor visitor = new PostgreSqlResourceQueryVisitor("id"); + query.accept(visitor); + List ids = queryIds(visitor); + assertEquals(new HashSet<>(expected), new HashSet<>(ids)); + } + + private List queryIds(PostgreSqlQueryVisitor visitor) throws SQLException { + String sql = visitor.getSql(); + logger.info("\n" + sql); + List ids = new ArrayList<>(); + try (Connection con = openConn()) { + try (PreparedStatement ps = con.prepareStatement(sql)) { + visitor.setValues(ps); + + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + ids.add(rs.getString(1)); + } + } + } + + return ids; + } +} diff --git a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/RealmTest.java b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/RealmTest.java new file mode 100644 index 000000000..d635ba916 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/RealmTest.java @@ -0,0 +1,130 @@ +/* + * 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.persistence.postgresql.dao.test.CachedDaoTest.dropSchema; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.File; +import java.sql.SQLException; + +import li.strolch.agent.api.StrolchRealm; +import li.strolch.agent.impl.DataStoreMode; +import li.strolch.model.ModelGenerator; +import li.strolch.model.Resource; +import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.runtime.privilege.PrivilegeHandler; +import li.strolch.testbase.runtime.AbstractModelTest; +import li.strolch.testbase.runtime.RuntimeMock; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import ch.eitchnet.privilege.model.Certificate; + +public class RealmTest extends AbstractModelTest { + + private static final String TESTUSER2 = "testuser2"; //$NON-NLS-1$ + private static final String TESTUSER1 = "testuser1"; //$NON-NLS-1$ + private static final String SECOND = "second"; //$NON-NLS-1$ + private static final String TEST = "test"; //$NON-NLS-1$ + private static final String FIRST = "first"; //$NON-NLS-1$ + + public static final String RUNTIME_PATH = "target/realmtest/"; //$NON-NLS-1$ + public static final String DB_STORE_PATH_DIR = "dbStore"; //$NON-NLS-1$ + public static final String CONFIG_SRC = "src/test/resources/realmtest"; //$NON-NLS-1$ + + protected static RuntimeMock runtimeMock; + + @Override + protected RuntimeMock getRuntimeMock() { + return runtimeMock; + } + + @BeforeClass + public static void beforeClass() throws SQLException { + + dropSchema("jdbc:postgresql://localhost/testdb1", TESTUSER1, TEST); //$NON-NLS-1$ + dropSchema("jdbc:postgresql://localhost/testdb2", TESTUSER2, TEST); //$NON-NLS-1$ + + File rootPath = new File(RUNTIME_PATH); + File configSrc = new File(CONFIG_SRC); + runtimeMock = new RuntimeMock(); + runtimeMock.mockRuntime(rootPath, configSrc); + new File(rootPath, DB_STORE_PATH_DIR).mkdir(); + runtimeMock.startContainer(); + } + + @Before + public void before() { + this.realmName = SECOND; + } + + @Test + public void testDifferentRealms() { + + String expectedId1 = "@realmTestId1"; //$NON-NLS-1$ + String expectedId2 = "@realmTestId2"; //$NON-NLS-1$ + String type = "Bla"; //$NON-NLS-1$ + + PrivilegeHandler privilegeHandler = runtimeMock.getAgent().getContainer().getPrivilegeHandler(); + Certificate certificate = privilegeHandler.authenticate(TEST, TEST.getBytes()); + + { + StrolchRealm firstRealm = runtimeMock.getRealm(FIRST); + assertEquals(DataStoreMode.TRANSACTIONAL, firstRealm.getMode()); + Resource expectedRes1 = ModelGenerator.createResource(expectedId1, "Bla bla", type); //$NON-NLS-1$ + try (StrolchTransaction tx = firstRealm.openTx(certificate, TEST)) { + tx.getResourceMap().add(tx, expectedRes1); + } + + try (StrolchTransaction tx = firstRealm.openTx(certificate, TEST)) { + Resource res = tx.getResourceMap().getBy(tx, type, expectedId1); + assertEquals("Should find object previously added in same realm!", expectedRes1, res); //$NON-NLS-1$ + } + } + + { + StrolchRealm secondRealm = runtimeMock.getRealm(SECOND); + assertEquals(DataStoreMode.TRANSACTIONAL, secondRealm.getMode()); + Resource expectedRes2 = ModelGenerator.createResource(expectedId2, "Bla bla", type); //$NON-NLS-1$ + try (StrolchTransaction tx = secondRealm.openTx(certificate, TEST)) { + tx.getResourceMap().add(tx, expectedRes2); + } + + try (StrolchTransaction tx = secondRealm.openTx(certificate, TEST)) { + Resource res = tx.getResourceMap().getBy(tx, type, expectedId2); + assertEquals("Should find object previously added in same realm!", expectedRes2, res); //$NON-NLS-1$ + } + } + + { + StrolchRealm secondRealm = runtimeMock.getRealm(SECOND); + try (StrolchTransaction tx = secondRealm.openTx(certificate, TEST)) { + Resource res = tx.getResourceMap().getBy(tx, type, expectedId1); + assertNull("Should not find object added in differenct realm!", res); //$NON-NLS-1$ + } + } + } + + @AfterClass + public static void afterClass() { + runtimeMock.destroyRuntime(); + } +} diff --git a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/TransactionalDaoTest.java b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/TransactionalDaoTest.java new file mode 100644 index 000000000..331b50e40 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/TransactionalDaoTest.java @@ -0,0 +1,62 @@ +/* + * 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.persistence.postgresql.dao.test.CachedDaoTest.DB_PASSWORD; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_URL; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.DB_USERNAME; +import static li.strolch.persistence.postgresql.dao.test.CachedDaoTest.dropSchema; + +import java.io.File; +import java.sql.SQLException; + +import li.strolch.testbase.runtime.AbstractModelTest; +import li.strolch.testbase.runtime.RuntimeMock; + +import org.junit.AfterClass; +import org.junit.BeforeClass; + +public class TransactionalDaoTest extends AbstractModelTest { + + public static final String RUNTIME_PATH = "target/transactionalStrolchRuntime/"; //$NON-NLS-1$ + public static final String DB_STORE_PATH_DIR = "dbStore"; //$NON-NLS-1$ + public static final String CONFIG_SRC = "src/test/resources/transactionalruntime"; //$NON-NLS-1$ + + protected static RuntimeMock runtimeMock; + + @Override + protected RuntimeMock getRuntimeMock() { + return runtimeMock; + } + + @BeforeClass + public static void beforeClass() throws SQLException { + + dropSchema(DB_URL, DB_USERNAME, DB_PASSWORD); + + File rootPath = new File(RUNTIME_PATH); + File configSrc = new File(CONFIG_SRC); + runtimeMock = new RuntimeMock(); + runtimeMock.mockRuntime(rootPath, configSrc); + new File(rootPath, DB_STORE_PATH_DIR).mkdir(); + runtimeMock.startContainer(); + } + + @AfterClass + public static void afterClass() { + runtimeMock.destroyRuntime(); + } +} diff --git a/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeConfig.xml b/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeConfig.xml new file mode 100644 index 000000000..9d7a227e3 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeConfig.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeModel.xml b/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeModel.xml new file mode 100644 index 000000000..0ed6ce7b2 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeModel.xml @@ -0,0 +1,39 @@ + + + + + + SYSTEM + + agent + + + + Application + Administrator + ENABLED + en_GB + + PrivilegeAdmin + AppUser + + + + + + + + + true + + + + + true + + + true + + + + \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/StrolchConfiguration.xml b/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/StrolchConfiguration.xml new file mode 100644 index 000000000..fe782ba2d --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/StrolchConfiguration.xml @@ -0,0 +1,43 @@ + + + + + StrolchPersistenceTest + + true + + + + PrivilegeHandler + li.strolch.runtime.privilege.PrivilegeHandler + li.strolch.runtime.privilege.DefaultStrolchPrivilegeHandler + + PrivilegeConfig.xml + + + + RealmHandler + li.strolch.agent.api.RealmHandler + li.strolch.agent.impl.DefaultRealmHandler + PrivilegeHandler + PersistenceHandler + + TRANSACTIONAL + true + true + + + + PersistenceHandler + li.strolch.persistence.api.PersistenceHandler + li.strolch.persistence.postgresql.PostgreSqlPersistenceHandler + + true + true + jdbc:postgresql://localhost/testdb + testuser + test + + + + \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/test/resources/log4j.xml b/li.strolch.persistence.postgresql/src/test/resources/log4j.xml new file mode 100644 index 000000000..0a2a73d06 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/resources/log4j.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml b/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml new file mode 100644 index 000000000..9d7a227e3 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeModel.xml b/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeModel.xml new file mode 100644 index 000000000..0ed6ce7b2 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeModel.xml @@ -0,0 +1,39 @@ + + + + + + SYSTEM + + agent + + + + Application + Administrator + ENABLED + en_GB + + PrivilegeAdmin + AppUser + + + + + + + + + true + + + + + true + + + true + + + + \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/StrolchConfiguration.xml b/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/StrolchConfiguration.xml new file mode 100644 index 000000000..e723d0aba --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/StrolchConfiguration.xml @@ -0,0 +1,50 @@ + + + + + StrolchPersistenceTest + + true + + + + PrivilegeHandler + li.strolch.runtime.privilege.PrivilegeHandler + li.strolch.runtime.privilege.DefaultStrolchPrivilegeHandler + + PrivilegeConfig.xml + + + + RealmHandler + li.strolch.agent.api.RealmHandler + li.strolch.agent.impl.DefaultRealmHandler + PrivilegeHandler + PersistenceHandler + + first, second + TRANSACTIONAL + TRANSACTIONAL + true + true + + + + PersistenceHandler + li.strolch.persistence.api.PersistenceHandler + li.strolch.persistence.postgresql.PostgreSqlPersistenceHandler + + true + true + + jdbc:postgresql://localhost/testdb1 + testuser1 + test + + jdbc:postgresql://localhost/testdb2 + testuser2 + test + + + + \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeConfig.xml b/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeConfig.xml new file mode 100644 index 000000000..9d7a227e3 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeConfig.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeModel.xml b/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeModel.xml new file mode 100644 index 000000000..0ed6ce7b2 --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeModel.xml @@ -0,0 +1,39 @@ + + + + + + SYSTEM + + agent + + + + Application + Administrator + ENABLED + en_GB + + PrivilegeAdmin + AppUser + + + + + + + + + true + + + + + true + + + true + + + + \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/StrolchConfiguration.xml b/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/StrolchConfiguration.xml new file mode 100644 index 000000000..6f256379d --- /dev/null +++ b/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/StrolchConfiguration.xml @@ -0,0 +1,42 @@ + + + + + StrolchPersistenceTest + + true + + + + PrivilegeHandler + li.strolch.runtime.privilege.PrivilegeHandler + li.strolch.runtime.privilege.DefaultStrolchPrivilegeHandler + + PrivilegeConfig.xml + + + + RealmHandler + li.strolch.agent.api.RealmHandler + li.strolch.agent.impl.DefaultRealmHandler + PrivilegeHandler + PersistenceHandler + + TRANSACTIONAL + true + + + + PersistenceHandler + li.strolch.persistence.api.PersistenceHandler + li.strolch.persistence.postgresql.PostgreSqlPersistenceHandler + + true + true + jdbc:postgresql://localhost/testdb + testuser + test + + + + \ No newline at end of file