All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fuchss.objectcasket.tablemodule.impl.TableImpl Maven / Gradle / Ivy

Go to download

Object Casket is a simple O/R mapper that can be used together with the Java Persistence API (JPA). The aim is to provide a simple solution for small projects to store multi-related entities in a simple manner.

There is a newer version: 0.20.17
Show newest version
package org.fuchss.objectcasket.tablemodule.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;

import org.fuchss.objectcasket.common.CasketError.CE2;
import org.fuchss.objectcasket.common.CasketError.CE3;
import org.fuchss.objectcasket.common.CasketException;
import org.fuchss.objectcasket.common.Util;
import org.fuchss.objectcasket.sqlconnector.port.DatabaseObserver;
import org.fuchss.objectcasket.sqlconnector.port.PreCompiledStatement;
import org.fuchss.objectcasket.sqlconnector.port.SqlArg;
import org.fuchss.objectcasket.sqlconnector.port.SqlDatabase;
import org.fuchss.objectcasket.sqlconnector.port.SqlObject;
import org.fuchss.objectcasket.sqlconnector.port.SqlObjectFactory;
import org.fuchss.objectcasket.sqlconnector.port.TableAssignment;
import org.fuchss.objectcasket.tablemodule.port.Row;
import org.fuchss.objectcasket.tablemodule.port.Table;
import org.fuchss.objectcasket.tablemodule.port.TableObserver;

class TableImpl implements Table, DatabaseObserver {

	private final SqlDatabase db;
	private final SqlObjectFactory objFac;
	private final TableAssignment dbTab;
	private final TableAssignment pkTab;

	private boolean closed;
	private SqlArg pkArg;
	private PreCompiledStatement newRowStmt;
	private PreCompiledStatement deleteRowStmt;
	private PreCompiledStatement selectAllPKsStmt;
	private PreCompiledStatement selectRowByPkStmt;
	private CasketException exc;

	private final Map pkRowMap = new HashMap<>();
	private final Map myRows = new HashMap<>();
	private final Set observers = new HashSet<>();

	private final Map> rowSignature = new HashMap<>();
	private String pkName;
	private Class pkType;
	private boolean isAutoIncrementedPK;

	private final TableModuleImpl myModule;

	protected TableImpl(TableModuleImpl module, TableAssignment dbTab, TableAssignment pkTab, SqlDatabase db, SqlObjectFactory objFac) {
		this.myModule = module;
		this.dbTab = dbTab;
		this.pkTab = pkTab;
		this.db = db;
		this.objFac = objFac;
	}

	protected void initSignature(Map> signature, String pkName, boolean autoIncrement) throws CasketException {
		this.rowSignature.putAll(signature);
		this.pkName = pkName;
		this.pkType = signature.get(pkName);
		this.isAutoIncrementedPK = autoIncrement;
		this.init();
		this.db.attach(this, this.pkTab);
	}

	private void init() throws CasketException {
		HashSet pkArgSet = new HashSet<>();
		this.pkArg = this.db.mkSqlArg(this.dbTab, this.pkName, SqlArg.CMP.EQUAL);
		pkArgSet.add(this.pkArg);
		this.newRowStmt = this.db.mkNewRowStmt(this.dbTab);
		this.deleteRowStmt = this.db.mkDeleteStmt(this.dbTab, pkArgSet, SqlArg.OP.AND);
		this.selectAllPKsStmt = this.db.mkSelectStmt(this.pkTab, new HashSet<>(), SqlArg.OP.AND);
		this.selectRowByPkStmt = this.db.mkSelectStmt(this.dbTab, pkArgSet, SqlArg.OP.AND);
	}

	@Override
	public synchronized boolean register(TableObserver observer) {
		return this.observers.add(observer);
	}

	@Override
	public synchronized boolean deregister(TableObserver observer) {
		return this.observers.remove(observer);
	}

	@Override
	public synchronized void update(TableAssignment tabOrView, List changed, List deleted, List added) {
		if (tabOrView != this.pkTab)
			return;

		Set changedRows = this.updateChanged(changed);
		Set deletedRows = this.updateDeleted(deleted);
		Set addedRows = this.updateAdded(added);
		changedRows.removeAll(deletedRows);
		if (changedRows.isEmpty() && deletedRows.isEmpty() && addedRows.isEmpty())
			return;
		this.observers.forEach(obs -> obs.update(changedRows, deletedRows, addedRows));
	}

	private Set updateChanged(List changed) {
		Set changedRows = new HashSet<>();
		for (SqlObject obj : changed) {
			RowImpl row = this.pkRowMap.get(obj.get(this.pkType));
			if (row != null) {
				row.hasChanged();
				changedRows.add(row);
			}
		}
		return changedRows;
	}

	private Set updateDeleted(List deleted) {
		Set deletedRows = new HashSet<>();
		for (SqlObject obj : deleted) {
			Object pk = obj.get(this.pkType);
			RowImpl row = this.pkRowMap.get(pk);
			if (row != null) {
				row.delete();
				deletedRows.add(row);
			}
			this.pkRowMap.remove(pk);
			this.myRows.remove(row);
		}
		return deletedRows;
	}

	private Set updateAdded(List added) {
		Set addedRows = new HashSet<>();
		for (SqlObject obj : added) {
			Serializable pk = obj.get(this.pkType);
			Map args = new HashMap<>();
			try {
				args.put(this.pkArg, this.objFac.mkSqlObject(this.dbTab.storageClass(this.pkName), pk));
				Map rowSqlValues = this.db.select(this.selectRowByPkStmt, args, null).get(0);
				addedRows.add(this.updateMaps(this.mkRow(rowSqlValues, pk), pk, null));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return addedRows;
	}

	protected void close() throws CasketException {
		if (this.closed)
			throw CE2.ALREADY_CLOSED.defaultBuild("table", this.getTableName());
		this.closed = true;
		this.db.detach(this, this.pkTab);
		this.newRowStmt.close();
		this.deleteRowStmt.close();
		this.selectAllPKsStmt.close();
		this.selectRowByPkStmt.close();
	}

	@Override
	public synchronized Row createRow(Map values, Object voucher) throws CasketException {
		this.checkArgs(values, voucher);
		this.myModule.checkVoucher(voucher);
		try {
			Map sqlValues = this.javaToSql(values, false);
			Serializable pk = this.db.newRow(this.newRowStmt, sqlValues, voucher).get(this.pkName).get(this.pkType);
			RowImpl newRow = new RowImpl(this, values, pk);
			this.pkRowMap.put(pk, newRow);
			this.myRows.put(newRow, newRow);
			this.myModule.add2created(this, newRow);
			return newRow;
		} catch (CasketException e) {
			this.myModule.rollback(e, voucher);
			return null; // never reached!
		}
	}

	@Override
	public synchronized void updateRow(Row row, Map values, Object voucher) throws CasketException {
		RowImpl theRow = this.myRows.get(row);
		this.checkArgs(theRow, values);
		this.myModule.checkVoucher(voucher);
		try {
			Map sqlValues = this.javaToSql(values, true);
			PreCompiledStatement updateRowStmt = this.db.mkUpdateRowStmt(this.dbTab, values.keySet());
			this.db.updateRow(updateRowStmt, this.mkSqlObject(this.pkName, theRow.getPk(this.pkType)), sqlValues, voucher);
			for (Entry entry : values.entrySet())
				theRow.setValue(entry.getKey(), entry.getValue());
			this.myModule.add2changed(this, theRow);
		} catch (CasketException e) {
			this.myModule.rollback(e, voucher);
		}
	}

	@Override
	public synchronized void deleteRow(Row row, Object voucher) throws CasketException {
		RowImpl theRow = this.myRows.get(row);
		this.checkArgs(theRow);
		this.myModule.checkVoucher(voucher);
		try {
			Map args = new HashMap<>();
			args.put(this.pkArg, this.objFac.mkSqlObject(this.dbTab.storageClass(this.pkName), theRow.getPk(this.pkType)));
			List keys = this.db.delete(this.deleteRowStmt, args, voucher);
			if ((keys == null) || (keys.size() != 1) || (this.pkRowMap.get(keys.get(0).get(this.pkType)) != theRow))
				throw CE3.UNEXPECTED_DELETE.defaultBuild(this.getTableName(), row, theRow.getPk(this.pkType));
			theRow.delete();
			this.myModule.add2deleted(this, theRow);
		} catch (CasketException e) {
			this.myModule.rollback(e, voucher);

		}
	}

	private List searchRows(HashSet argSet, Map args, Object voucher) throws CasketException {
		PreCompiledStatement selectRowStmt = this.db.mkSelectStmt(this.dbTab, argSet, SqlArg.OP.AND);
		List selectedRows = new ArrayList<>();
		for (Map RowSqlValues : this.db.select(selectRowStmt, args, voucher)) {
			RowImpl row = this.mkRow(RowSqlValues, null);
			Object pk = row.getPk(this.pkType);
			selectedRows.add(this.updateMaps(row, pk, voucher));
		}
		return selectedRows;

	}

	@Override
	public synchronized List allRows(Object voucher) throws CasketException {
		List> allPks = this.db.select(this.selectAllPKsStmt, new HashMap<>(), voucher);
		Set pkObjects = new HashSet<>();
		allPks.forEach(pkPair -> pkObjects.add(pkPair.get(this.pkName).get(this.pkType)));
		for (Serializable pk : pkObjects) {
			Row row = this.pkRowMap.get(pk);
			if ((row != null) && !row.isDirty())
				continue;
			Map args = new HashMap<>();
			args.put(this.pkArg, this.objFac.mkSqlObject(this.dbTab.storageClass(this.pkName), pk));
			Map rowSqlValues = this.db.select(this.selectRowByPkStmt, args, voucher).get(0);
			this.updateMaps(this.mkRow(rowSqlValues, pk), pk, voucher);
		}
		return new ArrayList<>(this.pkRowMap.values());

	}

	private RowImpl updateMaps(RowImpl row, Object pk, Object voucher) throws CasketException {
		RowImpl oldRow = this.pkRowMap.get(pk);
		if (oldRow == null) {
			this.pkRowMap.put(pk, row);
			this.myRows.put(row, row);
			return row;
		}
		if ((voucher != null) && oldRow.isDirty())
			this.reloadRow(oldRow, voucher);
		return oldRow;
	}

	@Override
	public synchronized List searchRows(Set cmpDef, Object voucher) throws CasketException {
		Objects.requireNonNull(cmpDef);
		HashSet argSet = new HashSet<>();
		Map args = new HashMap<>();
		for (Exp exp : cmpDef) {
			CompareObjectImpl cmpObj = this.mkCmpObject(exp.columnName(), exp.value(), exp.op());
			argSet.add(cmpObj.sqlArg);
			args.put(cmpObj.sqlArg, cmpObj.sqlObj);
		}
		return this.searchRows(argSet, args, voucher);
	}

	private  CompareObjectImpl mkCmpObject(String column, T value, TabCMP op) throws CasketException {
		Util.objectsNotNull(column, value, op);
		SqlObject sqlObj = this.objFac.mkSqlObject(this.dbTab.storageClass(column), value);
		SqlArg sqlArg = this.db.mkSqlArg(this.dbTab, column, CompareObjectImpl.map.get(op));
		return new CompareObjectImpl(this, column, sqlArg, sqlObj);

	}

	private  RowImpl mkRow(Map rowSqlValues, T pk) {
		Map rowValues = new HashMap<>();
		rowSqlValues.forEach((col, sqlObj) -> rowValues.put(col, sqlObj.get(this.getColumnType(col))));
		return new RowImpl(this, rowValues, pk == null ? rowValues.get(this.pkName) : pk);
	}

	@Override
	public void reloadRow(Row row, Object voucher) throws CasketException {
		RowImpl theRow = this.myRows.get(row);
		Objects.requireNonNull(theRow);
		Map args = new HashMap<>();
		args.put(this.pkArg, this.objFac.mkSqlObject(this.dbTab.storageClass(this.pkName), row.getPk(this.pkType)));
		Map rowSqlValues = this.db.select(this.selectRowByPkStmt, args, voucher).get(0);
		for (Entry entry : rowSqlValues.entrySet()) {
			String col = entry.getKey();
			if (col.equals(this.pkName))
				continue;
			theRow.setValue(col, entry.getValue().get(this.getColumnType(col)));
		}

	}

	protected Class getColumnType(String column) {
		return this.rowSignature.get(column);
	}

	protected Set allColumns() {
		return this.rowSignature.keySet();
	}

	protected String pkName() {
		return this.pkName;
	}

	private  SqlObject mkSqlObject(String column, T value) {
		try {
			return this.objFac.mkSqlObject(this.dbTab.storageClass(column), value);
		} catch (CasketException e) {
			this.exc = e;
		}
		return null;
	}

	private void throwIfError() throws CasketException {
		if (this.exc != null) {
			CasketException e = this.exc;
			this.exc = null;
			throw e;
		}
	}

	private void checkArgs(Object... args) throws CasketException {
		if (this.closed)
			throw CE2.ALREADY_CLOSED.defaultBuild("table", this.getTableName());
		Util.objectsNotNull(args);
	}

	private Map javaToSql(Map values, boolean valuesOnly) throws CasketException {
		Map sqlValues = new HashMap<>();
		if (valuesOnly)
			values.keySet().forEach(column -> sqlValues.put(column, this.mkSqlObject(column, values.get(column))));
		else
			this.dbTab.columnNames().forEach(column -> sqlValues.put(column, this.mkSqlObject(column, values.get(column))));
		this.throwIfError();
		return sqlValues;
	}

	protected void rollback(Set rows) {
		if (rows == null)
			return;
		try {
			for (Row row : rows) {
				RowImpl theRow = this.myRows.remove(row);
				Object pk = theRow == null ? null : theRow.getPk(this.pkType);
				if (pk == null)
					continue;
				this.pkRowMap.remove(pk);
				if (this.isAutoIncrementedPK)
					theRow.resetPK();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	String getTableName() {
		return this.dbTab.tableName();
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy