package jeffaschenk.commons.touchpoint.model.dao; import jeffaschenk.commons.touchpoint.model.*; import jeffaschenk.commons.types.StatusOutputType; import jeffaschenk.commons.util.NumberUtility; import jeffaschenk.commons.util.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.*; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.ProjectionList; import org.hibernate.criterion.Projections; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate4.SessionFactoryUtils; import org.springframework.orm.hibernate4.SessionHolder; import org.springframework.orm.hibernate4.support.HibernateDaoSupport; import org.springframework.stereotype.Repository; import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.persistence.Table; import java.util.*; /** * Generic Entity Service DAO Implementation. * * @author JeffASchenk@gmail.com * @version $Id: $ */ @Repository("entityDAO") public abstract class EntityDAOImpl extends HibernateDaoSupport implements EntityDAO { /** * Logging */ private static Log log = LogFactory.getLog(EntityDAOImpl.class); /** * Spring Injected Session Factory * * @param sessionFactory */ @Autowired public void setTouchPointSessionFactory(SessionFactory sessionFactory) { super.setSessionFactory(sessionFactory); } /** * Helper List of Associated Entity Classes. */ private static List<Class<? extends RootElement>> associatedClasses = new ArrayList<>(15); static { associatedClasses.add(Action.class); associatedClasses.add(Ancestry.class); associatedClasses.add(AncestryElement.class); associatedClasses.add(Element.class); associatedClasses.add(Group.class); associatedClasses.add(Owner.class); associatedClasses.add(Property.class); associatedClasses.add(PropertyValue.class); associatedClasses.add(SysEnvironment.class); } ; /** * Get a Low-Level Session for performing Query Specific Functions. * * @return Session */ public Session getDAOSession() { try { Session session = this.getSessionFactory().openSession(); if (TransactionSynchronizationManager.getResource(this.getSessionFactory()) == null) { TransactionSynchronizationManager.bindResource(this.getSessionFactory(), new SessionHolder(session)); } if (log.isDebugEnabled()) { log.debug("Successfully Started DAO Session with Extended Reach"); } return session; } catch (Exception e) { log.error( "Exception encountered while starting DAO Session with Extended Reach:" + e.getMessage(), e); throw new RuntimeException( "DAO Session Reach Start Up Exception Encountered", e); } } /** * Obtain the Low-Level Current Session. * * @return Session which is Current Active Session or newly created Session. */ public Session getCurrentDAOSession() { try { SessionHolder holder = (SessionHolder) TransactionSynchronizationManager .getResource(this.getSessionFactory()); if (holder == null) { return getDAOSession(); } // ********************************** // Obtain Session within Holder. Session session = holder.getSession(); if (session != null) { return session; } else { return getDAOSession(); } } catch (Exception e) { log.error("Exception encountered while Obtaining Current Session:" + e.getMessage(), e); throw new RuntimeException( "Exception encountered while Obtaining Current Session Encountered", e); } } /** * Finalize Obtained Session */ public void finalizeDAOSession() { if (log.isDebugEnabled()) { log.debug("Finishing Extended Session Reach"); } try { SessionHolder holder = (SessionHolder) TransactionSynchronizationManager .getResource(this.getSessionFactory()); if (holder != null) { Session s = holder.getSession(); s.flush(); TransactionSynchronizationManager .unbindResource(this.getSessionFactory()); SessionFactoryUtils.closeSession(s); } } catch (org.springframework.dao.DataIntegrityViolationException dive) { //getSQLConstraintLookupService().determineConstraintViolation(dive); log.error("Data Integrity Violation Exception has occurred!", dive); } catch (org.hibernate.exception.ConstraintViolationException cve) { //getSQLConstraintLookupService().determineConstraintViolation(cve); log.error("Constraint Violation Exception has occurred!", cve); } catch (Exception e) { log.error( "Exception encountered while finishing Extended Session Reach:" + e.getMessage(), e); throw new RuntimeException( "Extended Session Reach Tear Down Exception Encountered", e); } if (log.isDebugEnabled()) { log.debug("Successfully Finished Extended Session Reach"); } } /** * Finalize Obtained Session after failure, flush not performed. */ public void finalizeDAOSessionAfterException() { if (log.isDebugEnabled()) { log.debug("Finishing Extended Session Reach"); } try { SessionHolder holder = (SessionHolder) TransactionSynchronizationManager .getResource(this.getSessionFactory()); if (holder != null) { Session s = holder.getSession(); TransactionSynchronizationManager .unbindResource(this.getSessionFactory()); SessionFactoryUtils.closeSession(s); } } catch (Exception e) { log.error( "Exception encountered while finishing Extended Session Reach:" + e.getMessage(), e); throw new RuntimeException( "Extended Session Reach Tear Down Exception Encountered", e); } if (log.isDebugEnabled()) { log.debug("Successfully Finished Extended Session Reach"); } } /** * {@inheritDoc} * <p/> * Create Entity */ @Override public Integer createEntity(RootElement rootElement) { if (rootElement == null) { throw new IllegalArgumentException( "RootElement is null, unable to persist null Entity Object."); } // ************************************************ // Persist the new Object and return Row ID. return (Integer) getHibernateTemplate().save(rootElement); } /** * {@inheritDoc} * <p/> * Create or Update Entity */ public void createOrUpdateEntity(RootElement rootElement) { if (rootElement == null) { throw new IllegalArgumentException( "RootElement is null, unable to persist null Entity Object."); } // ************************************************ // Persist the new Object and return Row ID. getHibernateTemplate().saveOrUpdate(rootElement); } /** * {@inheritDoc} * <p/> * Update Entity */ @Override public void updateEntity(RootElement rootElement) { if (rootElement == null) { throw new IllegalArgumentException( "RootElement is null, unable to update null Entity Object."); } // ***************************************** // Persist the Updated Object getHibernateTemplate().update(rootElement); } /** * {@inheritDoc} * <p/> * Delete/Remove Entity */ @Override public void removeEntity(RootElement rootElement) { if (rootElement == null) { throw new IllegalArgumentException( "RootElement is null, unable to remove null Entity Object."); } getHibernateTemplate().delete(rootElement); } /** * {@inheritDoc} * <p/> * Get the specified Object by it's Distinct Row Identifier or ID. First Hibernate should look in our * first level Cache, if not will dip down to Database to resolve. If the * object does not exist this will throw an Exception. */ @Override public <T extends RootElement> T readDistinctEntity(Class<T> clazz, Integer id, Object... optionalParameters) { if (clazz == null) { throw new IllegalArgumentException("Class is null, unable to obtain Distinct RootElement Object."); } else if (id == null) { throw new IllegalArgumentException("Id is null, unable to obtain Distinct RootElement Object."); } try { // ************************************************* // Perform a get on a specific Asset Object Class. // *Note: using "get" as opposed to "load" because "get" // will return null if the row is not found whereas // "load" will return an empty proxy (and not throw an // exception). // @see org.hibernate.Session#get(Class, java.io.Serializable) T t = getHibernateTemplate().get(clazz, id); if (t == null) { return t; } // ************************** // Check Optional Parameters //if (OptionalParameters.areOptionalParametersUsed(optionalParameters)) { // ObjectGraphResolver.resolve(t, optionalParameters); //} // ********************************* // Return Obtained Distinct Element. return t; } catch (org.springframework.orm.ObjectRetrievalFailureException orfe) { // This indicates the Object is not within the persistent store. return null; } catch (org.springframework.dao.DataAccessException dae) { log.error("Encountered Data Access Exception:[" + dae.getMessage() + "], returning Null to upstream caller", dae); return null; } // End of Exception processing. } /** * {@inheritDoc} * <p/> * Get a Count of Rows for an Entity. Allows us to determine * if this is an initial load or not. */ @Override public Long getRowCount(Class<? extends RootElement> clazz) { // *************************************** // Initialize Long resultCount = new Long(0); Session session = null; try { // *********************************************** // Create the Criteria from our obtained session, // simply only limiting by Class. session = this.getDAOSession(); Criteria criteria = session.createCriteria(clazz); ProjectionList projectionList = Projections.projectionList(); projectionList.add(Projections.rowCount()); criteria.setProjection(projectionList); // ******************************************** // Now perform the query and return the count. List<?> results = criteria.list(); // ******************************************** // Check for returned Projection. if (results == null) { return resultCount; } // ******************************************** // Obtain an Iterator to traverse result. Iterator<?> resultIterator = results.iterator(); if (!resultIterator.hasNext()) { return resultCount; } // ******************************************** // Get the Object Count. while (resultIterator.hasNext()) { resultCount = (Long) resultIterator.next(); } return resultCount; } finally { if (session != null) { this.finalizeDAOSession(); } } } /** * Find a List of Results using a Named query and Named Parameters * * @param queryName * @param paramNames * @param values * @return List<RootElement Results List. */ @Override public List findByNamedQueryAndNamedParam(String queryName, String[] paramNames, Object[] values) { // ******************************************* // Perform Search based upon Specified // Named Query and Specified Paramaters. return getHibernateTemplate() .findByNamedQueryAndNamedParam(queryName, paramNames, values); } /** * Find by Criteria * * @param detachedCriteria * @param start * @param pageSize * @return List of Results */ @Override public List findByCriteria(DetachedCriteria detachedCriteria, int start, int pageSize) { // ******************************************* // Perform Search based upon Specified // Named Query and Specified Paramaters. return getHibernateTemplate() .findByCriteria(detachedCriteria, start, pageSize); } /** * Find by Criteria * * @param detachedCriteria * @return List of Results */ @Override public List findByCriteria(DetachedCriteria detachedCriteria) { // ******************************************* // Perform Search based upon Specified // Named Query and Specified Paramaters. return getHibernateTemplate() .findByCriteria(detachedCriteria); } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public List<? extends RootElement> getAllElementsForClass(Class<? extends RootElement> clazz) { Query q = this.getCurrentDAOSession().getNamedQuery(clazz.getSimpleName() + ".findAllAsc"); List<? extends RootElement> elements = (List<? extends RootElement>) q.list(); return elements; } /** * Obtain the Status for the Database Component. * * @param statusOutputType * @return String */ @Override public String status(StatusOutputType statusOutputType) { StringBuilder sb = new StringBuilder(); // Provide Row counts of all applicable Tables if (statusOutputType.equals(StatusOutputType.TEXT)) { sb.append('\t' + "Row Counts: " + '\n'); } List<String> counts = getAllElementCounts(statusOutputType); ListIterator<String> countsIterator = counts.listIterator(); while (countsIterator.hasNext()) { if (statusOutputType.equals(StatusOutputType.TEXT)) { sb.append('\t' + countsIterator.next() + '\n'); } else { sb.append(countsIterator.next()); } } return sb.toString(); } /** * Helper Method to Obtain Counts of All Elements * * @param statusOutputType * @return List<String> */ private List<String> getAllElementCounts(StatusOutputType statusOutputType) { List<String> elementCounts = new ArrayList<String>(); if (statusOutputType.equals(StatusOutputType.HTML)) { elementCounts.add("<table><tr><td align=\042right\042><b>Entity Name</b></td><td align=\042right\042><b>Table Name</b></td><td align=\042right\042><b>Current Row Count</b></td></tr>"); } Iterator<Class<? extends RootElement>> iterator = associatedClasses.iterator(); while (iterator.hasNext()) { Class<? extends RootElement> elementClass = iterator.next(); String tableName = getTableName(elementClass); // Check Output if (statusOutputType.equals(StatusOutputType.HTML)) { elementCounts.add( "<tr><td align=\042right\042>" + elementClass.getSimpleName() + "</td><td align=\042right\042>" + tableName + "</td><td align=\042right\042><b>" + NumberUtility.formatRowCount(this.getRowCount(elementClass)) + "</td></tr>"); } else { elementCounts.add( elementClass.getSimpleName() + "-> " + tableName + ": " + NumberUtility.formatRowCount(this.getRowCount(elementClass))); } } if (statusOutputType.equals(StatusOutputType.HTML)) { elementCounts.add("</table>"); } return elementCounts; } /** * Helper Method to Obtain Counts of All Elements currently in database. * * @return Map<Class<? extends RootElement>, Long> */ @Override public Map<Class<? extends RootElement>, Long> getAllElementClassCounts() { Map<Class<? extends RootElement>, Long> elementCounts = new HashMap<>(); Iterator<Class<? extends RootElement>> iterator = associatedClasses.iterator(); while (iterator.hasNext()) { Class<? extends RootElement> elementClass = iterator.next(); elementCounts.put( elementClass, this.getRowCount(elementClass)); } return elementCounts; } /** * Obtain all Entities for the Specified Element Class. * * @param clazz * @return Number of Elements Removed from Table. */ @Override public Number removeAllElementsForClass(Class<? extends RootElement> clazz) { if (clazz == null) { return 0L; } else if ((clazz.getName().toLowerCase().contains("demographics")) || (clazz.getName().toLowerCase().contains("web_payment_transaction")) || (clazz.getName().toLowerCase().contains("system"))) { logger.warn("Ignoring Clear of Class:[" + clazz.getName() + "], as Table needs to Maintain Integrity of Life-Cycles!"); return 0L; } // Perform deletion of all Rows and All Data for this Class/Table. String tableName = this.getTableName(clazz); if (StringUtils.isNotEmpty(tableName)) { SQLQuery q = this.getCurrentDAOSession().createSQLQuery("delete from " + tableName + " where 1=1"); Number resultRowsRemoved = q.executeUpdate(); logger.info("Clear of Class:[" + clazz.getName() + "], Table:[" + tableName + "], Successfully Cleared:[" + resultRowsRemoved + "] Rows/Objects from Table."); return resultRowsRemoved; } else { logger.warn("Ignoring Clear of Class:[" + clazz.getName() + "], as Table Name is unknown!"); return 0L; } } /** * Obtain the String name of the JDBC table for the Object. * * @return String Name of the Table annotated on the Entity Object. */ private String getTableName(Class<? extends RootElement> clazz) { try { RootElement element = clazz.newInstance(); Table tableAnnotation = element.getClass().getAnnotation(Table.class); return tableAnnotation == null ? null : tableAnnotation.name(); } catch (Exception e) { return null; } } }