package io.trygvis.persistence.sql; import javax.persistence.EntityManager; import javax.persistence.EntityTransaction; import javax.persistence.FlushModeType; import javax.persistence.LockModeType; import javax.persistence.PersistenceException; import javax.persistence.Query; import javax.persistence.RollbackException; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.metamodel.Metamodel; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; import java.util.List; import java.util.Map; import static javax.persistence.FlushModeType.AUTO; import static javax.persistence.LockModeType.NONE; public abstract class SqlEntityManager implements EntityManager { private Map properties = new HashMap<>(); public abstract SqlDao getDao(Class klass); // ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- private final SqlEntityManagerFactory factory; private final Connection c; private boolean autoCommit; private boolean open = true; private final Tx tx = new Tx(); protected Connection currentConnection() { return c; } // ----------------------------------------------------------------------- // EntityManager Implementation // ----------------------------------------------------------------------- protected SqlEntityManager(SqlEntityManagerFactory factory, Connection c) { this.factory = factory; this.c = c; try { autoCommit = c.getAutoCommit(); } catch (SQLException e) { throw new PersistenceException(e); } } @Override public void persist(Object entity) { if (entity == null) { throw new NullPointerException("entity"); } try { @SuppressWarnings("unchecked") Class klass = (Class) entity.getClass(); this.getDao(klass).insert(entity); } catch (SQLException e) { throw new PersistenceException(e); } } @Override public T merge(T entity) { if (entity == null) { throw new NullPointerException("entity"); } try { @SuppressWarnings("unchecked") Class klass = (Class) entity.getClass(); SqlDao dao = getDao(klass); T t = dao.selectById(entity); if (t == null) { dao.insert(entity); } else { dao.update(entity); } return t; } catch (SQLException e) { throw new PersistenceException(e); } } @Override public void remove(Object entity) { if (entity == null) { throw new NullPointerException("entity"); } try { @SuppressWarnings("unchecked") Class klass = (Class) entity.getClass(); this.getDao(klass).delete(entity); } catch (SQLException e) { throw new PersistenceException(e); } } @Override public T find(Class entityClass, Object primaryKey) { try { SqlDao dao = getDao(entityClass); return dao.selectById(primaryKey); } catch (SQLException e) { throw new PersistenceException(e); } } @Override public T find(Class entityClass, Object primaryKey, Map properties) { return find(entityClass, primaryKey); } @Override public T find(Class entityClass, Object primaryKey, LockModeType lockMode) { if (lockMode != NONE) { throw new IllegalArgumentException("Only lockMode = NONE is supported."); } return find(entityClass, primaryKey); } @Override public T find(Class entityClass, Object primaryKey, LockModeType lockMode, Map properties) { return find(entityClass, primaryKey, lockMode); } @Override public T getReference(Class entityClass, Object primaryKey) { throw new UnsupportedOperationException(); } @Override public void flush() { } @Override public void setFlushMode(FlushModeType flushMode) { if (flushMode != AUTO) { throw new RuntimeException("Flush mode not supported: " + flushMode); } } @Override public FlushModeType getFlushMode() { return AUTO; } @Override public void lock(Object entity, LockModeType lockMode) { throw new UnsupportedOperationException(); } @Override public void lock(Object entity, LockModeType lockMode, Map properties) { throw new UnsupportedOperationException(); } @Override public void refresh(Object entity) { throw new UnsupportedOperationException(); } @Override public void refresh(Object entity, Map properties) { throw new UnsupportedOperationException(); } @Override public void refresh(Object entity, LockModeType lockMode) { throw new UnsupportedOperationException(); } @Override public void refresh(Object entity, LockModeType lockMode, Map properties) { throw new UnsupportedOperationException(); } @Override public void clear() { } @Override public void detach(Object entity) { throw new UnsupportedOperationException(); } @Override public boolean contains(Object entity) { throw new UnsupportedOperationException(); } @Override public LockModeType getLockMode(Object entity) { throw new UnsupportedOperationException(); } @Override public void setProperty(String propertyName, Object value) { throw new UnsupportedOperationException(); } @Override public Map getProperties() { return properties; } @Override public Query createQuery(String qlString) { throw new UnsupportedOperationException(); } @Override public TypedQuery createQuery(CriteriaQuery criteriaQuery) { throw new UnsupportedOperationException(); } @Override public TypedQuery createQuery(String sqlString, Class resultClass) { // What happens if the transaction is aborted and this query is executes? Can a Query outlive it's connection? // Or even EntityManager? Should probably store a reference to the current connection and check that the // current one is the same when executing. SqlDao dao = this.getDao(resultClass); return new SqlQuery<>(dao, new SqlExecutorDelegate(), sqlString, true); } @Override public Query createNamedQuery(String name) { throw new UnsupportedOperationException(); } @Override public TypedQuery createNamedQuery(String name, Class resultClass) { throw new UnsupportedOperationException(); } @Override public Query createNativeQuery(String sql) { return new SqlQuery<>(null, new SqlExecutorDelegate(), sql, true); } @Override public Query createNativeQuery(String sql, Class resultClass) { SqlDao dao = getDao(resultClass); return new SqlQuery<>(dao, new SqlExecutorDelegate(), sql, true); } private class SqlExecutorDelegate implements SqlExecutor { // For now an EntityManager wraps a Connection private Connection getCurrentOrNewConnection() { return c; } @Override public int executeUpdate(UpdateCommand command) { if (!isOpen()) { throw new PersistenceException("This entity manager is closed."); } Connection c = getCurrentOrNewConnection(); try { return command.run(c); } catch (SQLException e) { throw new PersistenceException(e); } } @Override public List executeQuery(QueryCommand command) { if (!isOpen()) { throw new PersistenceException("This entity manager is closed."); } Connection c = getCurrentOrNewConnection(); try { return command.run(c); } catch (SQLException e) { throw new PersistenceException(e); } } } @Override public Query createNativeQuery(String sqlString, String resultSetMapping) { throw new UnsupportedOperationException(); } @Override public void joinTransaction() { throw new UnsupportedOperationException(); } @Override public T unwrap(Class cls) { if(cls == SqlUnit.class) { return cls.cast(this.factory.sqlUnit); } throw new PersistenceException(); } @Override public Object getDelegate() { throw new UnsupportedOperationException(); } @Override public void close() { try { c.close(); } catch (SQLException ignore) { } finally { open = false; } } @Override public boolean isOpen() { return open; } @Override public EntityTransaction getTransaction() { return tx; } @Override public SqlEntityManagerFactory getEntityManagerFactory() { throw new UnsupportedOperationException(); } @Override public CriteriaBuilder getCriteriaBuilder() { throw new UnsupportedOperationException(); } @Override public Metamodel getMetamodel() { throw new UnsupportedOperationException(); } private class Tx implements EntityTransaction { private boolean active; private boolean rollbackOnly; @Override public void begin() { try { if (autoCommit) { c.setAutoCommit(false); autoCommit = false; } active = true; rollbackOnly = false; } catch (SQLException e) { throw new PersistenceException(e); } } @Override public void commit() { if (rollbackOnly) { throw new RollbackException("Transaction marked for rollback only."); } if (autoCommit) { return; } try { c.commit(); } catch (SQLException e) { throw new PersistenceException(e); } active = true; } @Override public void rollback() { active = false; if (autoCommit) { return; } try { c.rollback(); } catch (SQLException e) { throw new PersistenceException(e); } } @Override public void setRollbackOnly() { this.rollbackOnly = active; } @Override public boolean getRollbackOnly() { return rollbackOnly; } @Override public boolean isActive() { return active; } } }