Java tutorial
/* Pivotal 5 Solutions Inc. - Core Java library for all other Pivotal Java Modules. * * Copyright (C) 2011 KASRA RASAEE * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.p5solutions.core.jpa.orm; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.Map; import javax.persistence.Table; import javax.sql.DataSource; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.PreparedStatementCallback; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import com.p5solutions.core.jpa.orm.DMLOperation.OperationType; import com.p5solutions.core.jpa.orm.annotations.FetchBeforeSave; import com.p5solutions.core.jpa.orm.entity.aop.EntityProxy; import com.p5solutions.core.jpa.orm.transaction.PersistenceContext; import com.p5solutions.core.jpa.orm.transaction.PersistenceContext.EntityState; import com.p5solutions.core.jpa.orm.transaction.PersistenceProvider; import com.p5solutions.core.jpa.orm.transaction.TransactionTemplate; import com.p5solutions.core.utils.ReflectionUtility; /** * EntityPersisterImpl: Implementation of the {@link EntityPersister} interface. The implementation helps in executing * dml operations for a given entity type, generated by the {@link EntityPersistUtility}. * * @author Kasra Rasaee * @since 2010-11-01 * * @see EntityPersistUtility generation of the basic dml operations used to persist data to the database * @see EntityParser for information on how to parse query results to a single-list of entities. * @see TransactionTemplate interface defining the rules for the transaction template implementation. * @see EntityDetail a class holding access methods and related data on a given entity or table-entity type. * @see ParameterBinder binding data for a single property within a given entity or table-entity * @see NamedParameterJdbcTemplate a name based paramaterization jdbc template by spring. * @see MapUtility and its implementation {@link MapUtilityImpl} maps a value to a given path within a object instance. * @see InterceptorUtility intercepting of entities before and after they have been saved / updated / merged / deleted. */ public class EntityPersisterImpl implements EntityPersister { /** The logger. */ private static Log logger = LogFactory.getLog(EntityPersisterImpl.class); /** The conversion utility. */ private ConversionUtility conversionUtility; /** The entity parser. */ private EntityParser entityParser; /** The map utility. */ private MapUtility mapUtility; /** The entity utility. */ private EntityUtility entityUtility; /** The data source. */ private DataSource dataSource; /** The jdbc template. */ private NamedParameterJdbcTemplate jdbcTemplate; /** The interceptor utility. */ private InterceptorUtility interceptorUtility; /** * The transaction template. a hook back into the transaction template; possible use, get next sequence value? */ private TransactionTemplate transactionTemplate; /** * Global fetch-before-save property that will affect saving any/all entities. */ private Boolean fetchBeforeSave; /** * Instantiates a new entity persister. */ public EntityPersisterImpl() { super(); } /** * Checks if is oracle data source. * * @return true, if is oracle data source * @see com.p5solutions.core.jpa.orm.EntityPersister#isOracleDataSource() */ @Override public boolean isOracleDataSource() { // TODO check for other types, possible abstract out return true; } /* * USING THE RETURNING CLAUSE IN A DML OPERATION COULD BE A "NICE TO HAVE" *//* * public boolean appendReturningClause(ParameterBinderExtended binder, StringBuilder returningColumns, * StringBuilder returningBinders) { * * if (Comparison.isNotEmpty(binder.getSequenceName())) { if (returningColumns.length() > 0) { // add a comman , * returningColumns.append(getSQLParameterSeparaterCharacter()); * returningBinders.append(getSQLParameterSeparaterCharacter()); } * * returningColumns.append(binder.getColumnName()); if (isNamedParameterJdbcTemplate()) { // Use param name since * we are using the // NamedParameterJdbcTemplate, template! returningBinders.append(getBindingCharacter()); * returningBinders.append(binder.getBindingName()); } else { //use index instead. ?? only if we use JdbcTemplate! * returningBinders.append("?"); } return true; } * * return false; } * * public void appendAll(ParameterBinderExtended binder, StringBuilder fields, StringBuilder values, StringBuilder * returningColumns, StringBuilder returningBinders) { * * appendField(binder, fields); appendValue(binder, values); //appendReturningClause(binder, returningColumns, * returningBinders); } */ /** * Checks if is named parameter jdbc template. * * @return true, if is named parameter jdbc template * @see com.p5solutions.core.jpa.orm.EntityPersister#isNamedParameterJdbcTemplate() */ @Override public boolean isNamedParameterJdbcTemplate() { // if its a standard jdbc template and not some named parameterized // template return getJdbcTemplate() instanceof NamedParameterJdbcTemplate; } /** * Gets the join column value. * * @param entity * the entity * @param pb * the pb * @return the join column value */ protected Object getJoinColumnValue(Object entity, ParameterBinder pb) { Object value = null; if (pb.isOneToOne()) { // throw not implemented yet. throw new RuntimeException( new NotImplementedException(pb.getEntityClass() + " - " + pb.getColumnNameAnyJoinOrColumn())); } else if (pb.isOneToMany()) { // TODO ignore, this is not a column, inverse join on the ManyToOne side } else if (pb.isManyToMany()) { String msg = "Many-to-many not supported plese check entity type " + pb.getEntityClass() + " and join column " + pb.getColumnNameAnyJoinOrColumn(); logger.error(msg); // throw not implemented yet. throw new RuntimeException(new NotImplementedException(msg)); } else if (pb.isManyToOne()) { // find value from the primary key. DependencyJoin dj = pb.getDependencyJoin(); // TODO check ?? at this point, DependencyJoin should never be null. // TODO probably build in a generic entity class dump / import such that // we can do testing on framework?? ParameterBinder joinPB = dj.getDependencyParameterBinder(); // temporarily store the join parameter such that we can get the parameter // binder value from it. Object temp = mapUtility.get(pb, entity, pb.getBindingPath()); // now get the value from the target join object, using its dependency // join // TODO does not support join columns! NOT YET.. value = mapUtility.get(joinPB, temp, joinPB.getBindingPath()); } return value; } /* * (non-Javadoc) * * @see com.p5solutions.core.jpa.orm.EntityPersister#executeDML(java.lang.String, java.util.Map) */ @Override public Long executeDML(String sql, Map<String, Object> params) { MapSqlParameterSource paramSource = new MapSqlParameterSource(params); // TODO <Object> should be of type T? how? import // org.apache.poi.hssf.record.formula.functions.T;?? Integer updated = getJdbcTemplate().execute(sql, paramSource, new PersistPreparedStatementCallback<Integer>()); return updated != null ? updated.longValue() : 0L; //return 0L; } /** * Process. * * @param entityClass * the entity class * @param entity * the entity * @return the map sql parameter source * @see com.p5solutions.core.jpa.orm.EntityPersister#process(java.lang.Class, java.lang.Object) */ @Override public MapSqlParameterSource process(Class<?> entityClass, Object entity) { // TODO needs serious cleaning up... some sort of state-machine type // processing. MapSqlParameterSource paramSource = new MapSqlParameterSource(); EntityDetail<?> entityDetail = getEntityUtility().getEntityDetail(entityClass); List<ParameterBinder> pbs = entityDetail.getParameterBinders(); boolean isDebug = logger.isDebugEnabled(); logger.debug("** Mapping values from entity -> " + entity.getClass() + " to DML parameter source."); for (ParameterBinder pb : pbs) { Object value = null; String bindingPath = pb.getBindingPath(); String bindingPathSQL = pb.getBindingPathSQL(); String debugMessage = " "; if (pb.isPrimaryKey()) { if (isDebug) { debugMessage += "[* Id] "; } // TODO probably a good idea to implement @EmbeddedId // TODO probably a good idea to implement @IdClass // let the below code handle the value retrieval. // value = mapUtility.get(pb, entity, bindingPath); } if (pb.isColumn()) { value = mapUtility.get(pb, entity, bindingPath); } else if (pb.isEmbedded()) { value = mapUtility.get(pb, entity, bindingPath); } else if (pb.isJoinColumn()) { value = getJoinColumnValue(entity, pb); } if (isDebug) { debugMessage = "Binding parameter [" + pb.toString() + "]"; } // check if the value is a generated value if (pb.isGeneratedValue() && value == null) { String sequenceName = pb.getSequenceName(); value = getTransactionTemplate().getSequenceValue(sequenceName); // Map the sequence value to the entity's parameter mapUtility.map(pb, entity, value, bindingPath); if (isDebug) { debugMessage += " <Generated> using sequence name " + sequenceName; } } if (isDebug) { if (value != null) { debugMessage += " with value of " + value; } else { debugMessage += " with value of <DBNull>"; } logger.debug(debugMessage); } // use the binding path as the binding name of the sql paramater // source since embedded, or join objects can be multi-level depths. paramSource.addValue(bindingPathSQL, value); } return paramSource; } /** * Gets the DML operation based on the table-entity class type and the operation type. * * @param clazz * the clazz * @param operationType * the operation type * @return the dML operation */ protected DMLOperation getDMLOperation(Class<?> clazz, OperationType operationType) { DMLOperation operation = getEntityUtility().getDMLOperation(clazz, operationType); if (operation == null) { String msg = "No " + operationType + " DML operation type found for table-entity of class type " + clazz + " please make sure it is part of the " + TransactionTemplate.class + " bean definition, and table is defined with the " + Table.class + " annotation!"; logger.error(msg); throw new NullPointerException(msg); } return operation; } /** * Save, Update, or Delete an entity within the database, use the operation type to suggest what to do. * * @param <T> * the generic type * @param entity * the entity * @param operationType * the operation type * @return the t */ @SuppressWarnings("unchecked") protected <T> T saveUpdateMergeOrDelete(T entity, OperationType operationType) { if (entity == null) { String msg = "** Cannot perform " + operationType + " DML operation on a null entity!, good luck finding the source! Suggestion, " + "use a conditional breakpoint 'entity == null' at the some call level step back to the root of the problem!"; logger.error(msg); throw new NullPointerException(msg); } // extract the target object before persistence. entity = EntityUtility.getTargetEntity(entity); Class<?> clazz = EntityUtility.getTargetEntityClass(entity); DMLOperation operation = getDMLOperation(clazz, operationType); String sql = operation.getStatement(); MapSqlParameterSource paramSource = process(clazz, entity); getJdbcTemplate().execute(sql, paramSource, new PersistPreparedStatementCallback<Integer>()); return entity; } /** * Save or update. * * @param <T> * the generic type * @param entity * the entity * @return the t * @throws Exception * the exception * @see com.p5solutions.core.jpa.orm.EntityPersister#saveOrUpdate(T) */ @Override public <T> T saveOrUpdate(T entity) throws Exception { if (entity == null) { String msg = "** Cannot perform INSERT or UPDATE DML operation on a null entity!, good luck finding the source! Suggestion, " + "use a conditional breakpoint 'entity == null' at the some call level step back to the root of the problem!"; logger.error(msg); throw new NullPointerException("Cannot save or update null entity"); } @SuppressWarnings("unchecked") Class<T> entityClass = EntityUtility.getTargetEntityClass(entity); EntityDetail<T> entityDetail = getEntityUtility().getEntityDetail(entityClass); // get the persistence context for the given thread PersistenceContext context = PersistenceProvider.get(); // if the entity is of entity proxy, then it must already be persisted // don't check the fetch-before-save, it has to be updated if (entity instanceof EntityProxy) { entity = update(entity); } else if (entityDetail.isPrimaryKeyNull(entity)) { // if the primary key of the entity is null, then it hasn't been persisted // don't check the fetch-before-save, it has to be inserted entity = save(entity); } else { // now, if the entity is neither a proxy and it has a valid primary key, // then we should check the persistence context for this transaction // to make sure that it already hasn't been persisted. String entityKey = entityDetail.getEntityKey(entity); // check the context if (context.exists(entityKey)) { // TODO throw error? perhaps a flag to determine if it should or not?? entity = update(entity); } else { // check the fetch-before-save to see whether we're doing the save or // not if (isFetchBeforeSaveWarranted(entityClass)) { // save it only if it is not already saved (not in context at this // point) if (getEntityParser().find(entity) == null) { entity = save(entity); } else { entity = update(entity); } } else { // fetch before save is not required so save it anyway entity = save(entity); } } } // add or update the entity state within the persistence context String entityKey = entityDetail.getEntityKey(entity); context.update(entityKey, entity, EntityState.SAVE); return entity; } /** * Verify if the fetch before save is to be done. * * @param entityClass * @return */ private boolean isFetchBeforeSaveWarranted(Class<?> entityClass) { return fetchBeforeSave || ReflectionUtility.hasAnyAnnotation(entityClass, FetchBeforeSave.class); } /** * Merge. * * @param <T> * the generic type * @param entity * the entity * @return the t * @throws Exception * the exception * @see com.p5solutions.core.jpa.orm.EntityPersister#merge(T) */ @Override public <T> T merge(T entity) throws Exception { // TODO fix me throw new NotImplementedException("the merge does not yet work properly, as such it is not ready for use"); // return saveUpdateMergeOrDelete(entity, OperationType.MERGE); } /** * Save. * * @param <T> * the generic type * @param entity * the entity * @return the t * @throws Exception * the exception * @see com.p5solutions.core.jpa.orm.EntityPersister#save(T) */ @Override public <T> T save(T entity) throws Exception { entity = getInterceptorUtility().beforeSave(entity); entity = saveUpdateMergeOrDelete(entity, OperationType.INSERT); entity = getInterceptorUtility().afterSave(entity); return entity; } /** * Update. * * @param <T> * the generic type * @param entity * the entity * @return the t * @throws Exception * the exception * @see com.p5solutions.core.jpa.orm.EntityPersister#update(T) */ @Override public <T> T update(T entity) throws Exception { entity = getInterceptorUtility().beforeUpdate(entity); entity = saveUpdateMergeOrDelete(entity, OperationType.UPDATE); entity = getInterceptorUtility().afterUpdate(entity); return entity; } /** * Delete. * * @param <T> * the generic type * @param entity * the entity * @return the int * @throws Exception * the exception * @see com.p5solutions.core.jpa.orm.EntityPersister#delete(T) */ @Override public <T> int delete(T entity) throws Exception { getInterceptorUtility().afterDelete(entity); Object ret = saveUpdateMergeOrDelete(entity, OperationType.DELETE); getInterceptorUtility().afterDelete(entity); // return the number of rows affected return ret instanceof Integer ? ((Integer) ret).intValue() : 0; } /** * Delete. * * @param <T> * the generic type * @param tableClass * the table class * @param id * the id * @return the int * @throws Exception * the exception * @see com.p5solutions.core.jpa.orm.EntityPersister#delete(java.lang.Class, java.lang.Object) */ @Override public <T> int delete(Class<T> tableClass, Object id) throws Exception { // T t = saveUpdateOrDelete(entity, OperationType.DELETE); // TODO check for null on entity detail EntityDetail<T> entityDetail = getEntityUtility().getEntityDetail(tableClass); // get the dml operation DMLOperation operation = getEntityUtility().getDMLOperation(tableClass, OperationType.DELETE); MapSqlParameterSource paramSource = new MapSqlParameterSource(); // build the parameter value list List<ParameterBinder> pkParameterBinders = entityDetail.getPrimaryKeyParameterBinders(); if (pkParameterBinders == null) { String msg = "Primary key parameter binders cannot be null for given table-entity class type of " + tableClass; logger.error(msg); throw new NullPointerException(msg); } else if (pkParameterBinders.size() != 1) { String msg = "There is a total of " + pkParameterBinders.size() + " when there should only be one, when calling delete(clazz, id);"; logger.error(msg); throw new RuntimeException(msg); } ParameterBinder pkpb = pkParameterBinders.get(0); paramSource.addValue(pkpb.getBindingName(), id); Integer updated = getJdbcTemplate().execute(operation.getStatement(), paramSource, new PersistPreparedStatementCallback<Integer>()); return updated; // return the number of rows affected //return ret instanceof Integer ? ((Integer) ret).intValue() : 0; } /** * The Class PersistPreparedStatementCallback. * * @param <T> * the generic type */ protected class PersistPreparedStatementCallback<T> implements PreparedStatementCallback<T> { /* * (non-Javadoc) * * @see org.springframework.jdbc.core.PreparedStatementCallback#doInPreparedStatement (java.sql.PreparedStatement) */ @SuppressWarnings("unchecked") @Override public T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException { // TODO Auto-generated method stub if (ps.execute()) { ResultSet rs = ps.getResultSet(); // TODO fix me... return null; } else { return (T) new Integer(ps.getUpdateCount()); } } } /** * Gets the data source. * * @return the data source * @see com.p5solutions.core.jpa.orm.EntityPersister#getDataSource() */ @Override public DataSource getDataSource() { return dataSource; } /** * Sets the data source. * * @param dataSource * the new data source * @see com.p5solutions.core.jpa.orm.EntityPersister#setDataSource(javax.sql.DataSource) */ @Override public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * Gets the jdbc template. * * @return the jdbc template * @see com.p5solutions.core.jpa.orm.EntityPersister#getJdbcTemplate() */ @Override public NamedParameterJdbcTemplate getJdbcTemplate() { if (jdbcTemplate == null) { jdbcTemplate = new NamedParameterJdbcTemplate(getDataSource()); } return jdbcTemplate; } /** * Sets the jdbc template. * * @param jdbcTemplate * the new jdbc template * @see com.p5solutions.core.jpa.orm.EntityPersister#setJdbcTemplate(org.springframework.jdbc * .core.namedparam.NamedParameterJdbcTemplate) */ @Override public void setJdbcTemplate(NamedParameterJdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } /** * Sets the entity utility. * * @param entityUtility * the new entity utility * @see com.p5solutions.core.jpa.orm.EntityPersister#setEntityUtility(com.p5solutions.core.jpa.orm. EntityUtility) */ @Override public void setEntityUtility(EntityUtility entityUtility) { this.entityUtility = entityUtility; } /** * Gets the entity utility. * * @return the entity utility * @see com.p5solutions.core.jpa.orm.EntityPersister#getEntityUtility() */ @Override public EntityUtility getEntityUtility() { return entityUtility; } /** * Sets the conversion utility. * * @param conversionUtility * the new conversion utility * @see com.p5solutions.core.jpa.orm.EntityPersister#setConversionUtility(com.p5solutions.core.jpa.orm * .ConversionUtility) */ @Override public void setConversionUtility(ConversionUtility conversionUtility) { this.conversionUtility = conversionUtility; } /** * Gets the conversion utility. * * @return the conversion utility * @see com.p5solutions.core.jpa.orm.EntityPersister#getConversionUtility() */ @Override public ConversionUtility getConversionUtility() { return conversionUtility; } @Override public EntityParser getEntityParser() { return entityParser; } @Override public void setEntityParser(EntityParser entityParser) { this.entityParser = entityParser; } /** * Gets the map utility. * * @return the map utility * @see com.p5solutions.core.jpa.orm.EntityPersister#getMapUtility() */ @Override public MapUtility getMapUtility() { return mapUtility; } /** * Sets the map utility. * * @param mapUtility * the new map utility * @see com.p5solutions.core.jpa.orm.EntityPersister#setMapUtility(com.p5solutions.core.jpa.orm.MapUtility ) */ @Override public void setMapUtility(MapUtility mapUtility) { this.mapUtility = mapUtility; } /** * Sets the transaction template. * * @param transactionTemplate * the new transaction template * @see com.p5solutions.core.jpa.orm.EntityPersister#setTransactionTemplate(com.p5solutions.core. * orm.transaction.TransactionTemplate) */ @Override public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } /** * Gets the transaction template. * * @return the transaction template * @see com.p5solutions.core.jpa.orm.EntityPersister#getTransactionTemplate() */ @Override public TransactionTemplate getTransactionTemplate() { return transactionTemplate; } /** * Sets the interceptor utility. * * @param interceptorUtility * the new interceptor utility * @see com.p5solutions.core.jpa.orm.EntityPersister#setInterceptorUtility(com.p5solutions.core.jpa.orm * .InterceptorUtility) */ @Override public void setInterceptorUtility(InterceptorUtility interceptorUtility) { this.interceptorUtility = interceptorUtility; } /** * Gets the interceptor utility. * * @return the interceptor utility * @see com.p5solutions.core.jpa.orm.EntityPersister#getInterceptorUtility() */ @Override public InterceptorUtility getInterceptorUtility() { return interceptorUtility; } /** * @return */ public Boolean getFetchBeforeSave() { return fetchBeforeSave; } /** * @param fetchBeforeSave */ public void setFetchBeforeSave(Boolean fetchBeforeSave) { this.fetchBeforeSave = fetchBeforeSave; } }