/* * Copyright 2013 Robert von Burg <eitch@eitchnet.ch> * * 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<T extends StrolchElement> implements StrolchDao<T> { protected PostgreSqlStrolchTransaction tx; protected List<DaoCommand> commands; public PostgresqlDao(PostgreSqlStrolchTransaction tx) { this.tx = tx; this.commands = new ArrayList<>(); } protected PostgreSqlStrolchTransaction tx() { return this.tx; } 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 = 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 = 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 = 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<String> queryKeySet() { Set<String> keySet = new HashSet<>(); String sql = "select id from " + getTableName(); try (PreparedStatement statement = 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<String> queryKeySet(String type) { Set<String> keySet = new HashSet<>(); String sql = "select id from " + getTableName() + " where type = ?"; try (PreparedStatement statement = 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<String> queryTypes() { Set<String> keySet = new HashSet<>(); String sql = "select distinct type from " + getTableName(); try (PreparedStatement statement = 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 = 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<T> queryAll() { List<T> list = new ArrayList<>(); String sql = "select id, name, type, asxml from " + getTableName(); try (PreparedStatement statement = 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<T> queryAll(String type) { List<T> list = new ArrayList<>(); String sql = "select id, name, type, asxml from " + getTableName() + " where type = ?"; try (PreparedStatement statement = 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<T> 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<T> 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<T> 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 = 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 = 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 = 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) { // even though we support rollback we can clear the commands here even if we performed them because the DB transaction will be rolled back for (DaoCommand command : this.commands) { command.doComand(txResult); } this.commands.clear(); } void rollback() { this.commands.clear(); } }