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.lang.reflect.Method; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import javax.persistence.AttributeOverride; import javax.persistence.AttributeOverrides; import javax.persistence.Column; import javax.persistence.Embeddable; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.JoinColumns; import javax.persistence.ManyToOne; import javax.persistence.NamedNativeQuery; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import javax.persistence.Table; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.jdbc.datasource.DataSourceUtils; import com.p5solutions.core.jpa.orm.DMLOperation.OperationType; import com.p5solutions.core.jpa.orm.annotations.EntityAnnotationScanner; import com.p5solutions.core.jpa.orm.entity.aop.EntityProxy; import com.p5solutions.core.jpa.orm.exceptions.AnnotationNotDefinedException; import com.p5solutions.core.jpa.orm.transaction.TransactionTemplate; import com.p5solutions.core.utils.Comparison; import com.p5solutions.core.utils.ReflectionUtility; /** * The Class EntityUtility: This utility will evaluate a list of entities, * including table-entities, and generate a list of {@link ParameterBinder}'s * which will help in addressing the basic operations of persistence and * selection of data from and to the database. * * @author Kasra Rasaee * @since 2010-10-29 * * @see EntityParser * @see EntityPersister * @see EntityPersistUtility * * @see TransactionTemplate * * @see EntityDetail for details on a specific entity type, based on class type */ public class EntityUtility { /** The logger. */ private static Log logger = LogFactory.getLog(EntityUtility.class); /** The cache entity details. */ private Hashtable<Class<?>, EntityDetail<?>> cacheEntityDetails; /** The cached global named native queries. */ private Map<String, NamedNativeQuery> cachedGlobalNamedNativeQuery = new HashMap<String, NamedNativeQuery>(); /** The entity persist utility. */ private EntityPersistUtility entityPersistUtility; /** List of entity packages to scan for entities. */ private List<String> entityPackages; /** * DataSource to retrieve meta data for given table entities, for example * column data type, size, ?null, so forth **/ private DataSource dataSource; @SuppressWarnings("unchecked") public static <T> Class<T> getTargetEntityClass(T entity) { Class<T> entityClass = (Class<T>) entity.getClass(); if (entity instanceof EntityProxy) { entityClass = (Class<T>) ((EntityProxy) entity).getTarget().getClass(); } return entityClass; } @SuppressWarnings("unchecked") public static <T> T getTargetEntity(T entity) { if (entity instanceof EntityProxy) { EntityProxy entityProxy = (EntityProxy) entity; entity = (T) entityProxy.getTarget(); } return entity; } /** * Initialize. Initialize all entities using Entity class scanner. */ public void initialize() { if (entityPackages != null) { EntityAnnotationScanner scanner = new EntityAnnotationScanner(); for (String entityPackage : entityPackages) { String typeName = null; try { for (Class<? extends AbstractEntity> type : scanner.getComponentClasses(entityPackage)) { typeName = type.getSimpleName(); EntityDetail<?> detail = getEntityDetail(type); // only generate dml operations for table annotations. Table table = ReflectionUtility.findAnnotation(type, Table.class); if (table != null) { // build the basic dml operations for persistence. getEntityPersistUtility().build(type); } } // build the meta data for all the entity tables. buildColumnMetaDataAll(); } catch (ClassNotFoundException e) { String msg = "Cannot initialize entity binders for given class name " + typeName + " because it does not exist within the provided classpath. " + "Please make sure the class exists, and there are no errors in the path and name."; logger.fatal(msg); throw new RuntimeException(msg); } catch (Exception e) { logger.fatal(e); throw new RuntimeException(e); } } } } /** * Set the datasource for this entity utility * * @param dataSource */ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * Get the datasource for this entity utility. * * @return */ protected DataSource getDataSource() { return this.dataSource; } /** * Throw entity class null. * * @param <T> * the generic type * @param entityClass * the entity class */ protected <T> void throwEntityClassNull(Class<T> entityClass) { if (entityClass == null) { throw new NullPointerException("Entity class cannot be null"); } } /** * Throw recursion filter list null. * * @param recursionFilterList * the recursion filter list */ protected void throwRecursionFilterListNull(List<Class<?>> recursionFilterList) { // / cannot have a null recursion filter list, this can cause a // recursive loop, which will blow the stack! if (recursionFilterList == null) { throw new NullPointerException("Recursion Filter List cannot be null, it is needed to " + "prevent recursive loops when building the object graph - e.g. via @" + JoinColumn.class + " annotations defined via an inverse annotation!"); } } /** * Gets the binding character. * * @return the binding character */ protected String getBindingCharacter() { // TODO should be unique per database source?? .NET is unique per database // type return ":"; } /** * Gets the sQL parameter separater character. * * @return the sQL parameter separater character */ protected String getSQLParameterSeparaterCharacter() { return ","; } /** * Format binding name. * * @param name * the name * @return the string */ protected String formatBindingName(String name) { return name; // return getBindingCharacter().concat(name); } /** * Returns a table name for a specified entity class (if found). If not found * it will return null. * * @param entityClass * @return */ public String getTableName(Class<? extends AbstractEntity> entityClass) { EntityDetail<?> entityDetail = cacheEntityDetails.get(entityClass); return entityDetail == null ? null : entityDetail.getTableName(); } /** * Gets the entity detail. * * @param <T> * the generic type * @param entityClass * the entity class * @return the entity detail */ public <T> EntityDetail<T> getEntityDetail(Class<T> entityClass) { List<Class<?>> recursionFilterList = new ArrayList<Class<?>>(); return getEntityDetail(entityClass, recursionFilterList); } /** * Gets the entity detail. * * @param <T> * the generic type * @param entityClass * the entity class * @param recursionFilterList * the recursion filter list * @return the entity detail */ public <T> EntityDetail<T> getEntityDetail(Class<T> entityClass, List<Class<?>> recursionFilterList) { if (EntityProxy.class.isAssignableFrom(entityClass)) { logger.debug("retreiving entity details for class " + entityClass); } if (cacheEntityDetails == null) { cacheEntityDetails = new Hashtable<Class<?>, EntityDetail<?>>(); } EntityDetail<T> entityDetail = null; if (!cacheEntityDetails.containsKey(entityClass)) { entityDetail = buildEntityDetail(entityClass, recursionFilterList); // this could return null for several reasons // one, it was unable to parse the entity details for any number of // exceptions, probably thrown and not hit this // or the filter list, filtered it out, since there was some sort of join // which inevitable returned to the // same entity type, for example either an inverse join, or a join which // joined against the same entity, // such as a recursive entity. if (entityDetail != null) { cacheEntityDetails.put(entityClass, entityDetail); } } else { // suppressed warning, since this will return // null, unless the entity class is of type T entityDetail = (EntityDetail<T>) cacheEntityDetails.get(entityClass); if (logger.isDebugEnabled()) { logger.debug( "** Found join-columns which inevitably resulted in a recursion. Returning entity class " + entityClass + " which is already being built as part of the dependency graph. This is just " + "informational, and NOT an error!"); } } return entityDetail; } /** * Builds an entity detail object which defines an entities parameters, and * related structure. * * @param <T> * the generic type * @param entityClass * the entity class * @param recursionFilterList * the recursion filter list * @return the entity detail */ protected <T> EntityDetail<T> buildEntityDetail(Class<T> entityClass, List<Class<?>> recursionFilterList) { // check for nulls throwEntityClassNull(entityClass); throwRecursionFilterListNull(recursionFilterList); // return null if this is part of the recursion list, meaning already // hit this // entity before. // if the join is not null, and the class of the parameter matches the class that we are trying to build the details for, then // we have a recursion entity which references itself, this means, that we cannot simply ignore the join-column, but rather // need to embrace it and build its dependency join column. if (recursionFilterList.contains(entityClass)) { return null; } // add this entity to the recursion filter list, so we can prevent // recursive loops! // *** NOTE // This will probably happen in a (entity x) OneToOne <> (entity y) // OneToOne relationship?? // *** // This will not happen on a ManyToOne <> OneToMany relationship // since Collections are not iterated (because its type is not known) // as such, the inverse join column is used to determine the dependency // of the entities (aka. tables) recursionFilterList.add(entityClass); EntityDetail<T> entityDetail = new EntityDetail<T>(entityClass); List<ParameterBinder> pbs = new ArrayList<ParameterBinder>(); // build a list of all parameters, including joins, embedded, columns, // ids, transients, etc. // this method will build the parameters from the root, and not as part // of another parent class build(entityClass, pbs, recursionFilterList); // setup attribute overrides, if any on the entity class setupAttributeOverrides(entityClass, pbs); // set all the parameters entityDetail.setParameters(pbs); // get all named native queries and append them to the global named native query list. buildGlobalNamedNativeQueries(entityDetail); // return the entity detail return entityDetail; } /** * Builds the global named native queries cache, in sequential order of * scanned entity. * * If duplicate keys are found, the last entities query name will take * precedence. * * @param <T> * the generic type * @param entityDetail * the entity detail */ protected <T> void buildGlobalNamedNativeQueries(EntityDetail<T> entityDetail) { Map<String, NamedNativeQuery> nativeQueries = entityDetail.getNamedNativeQueries(); if (nativeQueries != null) { logger.info("Populating named native queries in global cache for " + entityDetail.getEntityClass()); for (String key : nativeQueries.keySet()) { NamedNativeQuery newNativeQuery = nativeQueries.get(key); logger.debug(" -- query name: " + key); logger.debug(" --> query sql: [" + newNativeQuery + "]"); if (this.cachedGlobalNamedNativeQuery.containsKey(key)) { String warning = " -->> Warning, [" + key + "] already exists in global cache, check entity " + entityDetail.getEntityClass(); logger.warn(warning); } this.cachedGlobalNamedNativeQuery.put(key, newNativeQuery); } } } /** * Find named native query in the global cache, and not specific to a given * entity. * * @param name * the name * @return the named native query */ public NamedNativeQuery findGlobalNamedNativeQuery(String name) { if (this.cachedGlobalNamedNativeQuery == null) { return null; } return this.cachedGlobalNamedNativeQuery.get(name); } /** * Find any attribute override or overrides, and return a single list of all * of them. * * @param <T> * the generic type * @param entityClass * the entity class * @return the list */ protected <T> List<AttributeOverride> findAttributeOverrides(Class<T> entityClass) { List<AttributeOverride> overrides = new ArrayList<AttributeOverride>(); // try to add a single attribute override AttributeOverride ao = ReflectionUtility.findAnnotation(entityClass, AttributeOverride.class); if (ao != null) { overrides.add(ao); } // try to add all the attribute overrides AttributeOverrides aos = ReflectionUtility.findAnnotation(entityClass, AttributeOverrides.class); // if both attribute overrride and overrides is defined, throw exception if (ao != null && aos != null) { String error = "Cannot define entity with BOTH " + AttributeOverride.class + " & @" + AttributeOverrides.class; logger.error(error); throw new RuntimeException(error); } // if overrides is not empty or null, then add them all in if (aos != null) { for (AttributeOverride ao2 : aos.value()) { overrides.add(ao2); } } return Comparison.isEmptyOrNull(overrides) ? null : overrides; } /** * Setup attribute overrides. * * @param <T> * the generic type * @param entityClass * the entity class * @param pbs * the pbs */ protected <T> void setupAttributeOverrides(Class<T> entityClass, List<ParameterBinder> pbs) { List<AttributeOverride> overrides = findAttributeOverrides(entityClass); // not the most efficient way of doing this, but its not overwhelming, // and its only done once! if (!Comparison.isEmptyOrNull(overrides)) { for (AttributeOverride override : overrides) { boolean found = false; for (ParameterBinder pb : pbs) { if (override.name().equals(pb.getFieldName())) { found = true; pb.setOverrideColumn(override.column()); } } if (!found) { String error = "No override method found when using attribute-override in entity class " + entityClass + " when an attempt was made to match against field name " + override.name(); logger.error(error); throw new RuntimeException(new NoSuchFieldException(error)); } } } } /** * Builds the Parameter Binder list starting from a class (ideally the root * class). * * @param <T> * the generic type * @param clazz * the clazz * @param pbs * the pbs * @param recursionFilterList * the recursion filter list */ public <T> void build(Class<T> clazz, List<ParameterBinder> pbs, List<Class<?>> recursionFilterList) { // build root node build(clazz, "", null, null, null, pbs, recursionFilterList); } /** * Build the database-meta-data for all table entities. */ protected void buildColumnMetaDataAll() { Connection connection = null; try { connection = DataSourceUtils.getConnection(dataSource); for (EntityDetail<?> detail : this.cacheEntityDetails.values()) { Table table = detail.getTableAnnotation(); if (table != null) { buildColumnMetaData(table, detail, connection); } } } catch (Exception e) { logger.error(e.toString()); } finally { if (connection != null) { try { connection.close(); } catch (SQLException e) { ; } connection = null; } } } /** * Build the database-meta-dta for a given table entity, using an existing * connection. * * @param table * annotation * @param detail * {@link EntityDetail} probably provided by the * {@link #cacheEntityDetails} * @param connection * an existing mock or real, database connection. */ protected void buildColumnMetaData(Table table, EntityDetail<?> detail, Connection connection) { Statement stmt = null; ResultSet rs = null; try { String sql = "SELECT * FROM " + table.name() + " WHERE 1=0"; stmt = connection.createStatement(); // set the maximum result set to zero, just in-case!? stmt.setMaxRows(0); rs = stmt.executeQuery(sql); ResultSetMetaData rsMeta = rs.getMetaData(); logger.info("** Building Database MetaData for Table " + table.name()); for (int ic = 1; ic <= rsMeta.getColumnCount(); ic++) { String columnName = rsMeta.getColumnName(ic); ParameterBinder binder = detail.getParameterBinderByAny(columnName); if (binder == null) { if (logger.isErrorEnabled()) { String error = " -- Column " + columnName + " as defined by the table meta-data, cannot be found within the scope of " + detail.getEntityClass(); logger.error(error); } // TODO ?? throw new RuntimeException(new // NoColumnDefinedException(error)); } else { ParameterBinderColumnMetaData columnMetaData = new ParameterBinderColumnMetaData(); // columnMetaData.setColumnIndex(ic); // USELESS, EVERY UNIQUE QUERY // STRING WOULD RESULT IN A DIFFERENT INDEX. EASIER TO CACHE IT BASED // ON UNIQUE QUERY STRINGS. // columnMetaData.setColumnLabel(rsMeta.getColumnLabel(ic)); columnMetaData.setColumnName(columnName); columnMetaData.setLength(rsMeta.getColumnDisplaySize(ic)); columnMetaData.setPrecision(rsMeta.getPrecision(ic)); columnMetaData.setScale(rsMeta.getScale(ic)); columnMetaData.setColumnType(rsMeta.getColumnType(ic)); columnMetaData.setColumnTypeName(rsMeta.getColumnTypeName(ic)); binder.setColumnMetaData(columnMetaData); if (logger.isDebugEnabled()) { logger.debug(" -- [" + columnMetaData.toString() + "]"); } } } } catch (SQLException e) { logger.error(">> *UNABLE* to retrieve meta data for table " + table.name() + ", doesn't exist?"); } finally { if (rs != null) { try { rs.close(); } catch (SQLException e) { ; } rs = null; } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { ; } stmt = null; } } } /** * Builds the the Parameter Binder list starting from a class, not necessarily * the root class. * * @param <T> * the generic type * @param entityClass * the clazz * @param bindingPath * the binding path * @param parentClass * the parent class, <code>null</code> if root class * @param parentGetterMethod * the parent getter method, <code>null</code> if root class * @param parentSetterMethod * the parent setter method, <code>null</code> if root class * @param pbs * the pbs * @param recursionFilterList * the recursion filter list */ public <T> void build(Class<T> entityClass, String bindingPath, Class<?> parentClass, Method parentGetterMethod, Method parentSetterMethod, List<ParameterBinder> pbs, List<Class<?>> recursionFilterList) { List<Method> methods = ReflectionUtility.findGetMethodsWithNoParams(entityClass); // if the methods are blank or null then throw an exception, cannot be // an empty entity type if (Comparison.isEmptyOrNull(methods)) { String error = "No getters found for entity/embedded class type " + entityClass + " which represent a column property; make sure " + "the getters do no have any accepting parameters; such as getSomeField(...) { }"; logger.error(error); throw new NullPointerException(error); } int index = 1; // the parameter binding index in a Sql Parameter // // for (Method getterMethod : methods) { // @Transient methods are not to be used for binding if (ReflectionUtility.isTransient(getterMethod)) { continue; } // look up the setter method (must exist for non-transient getters) Method setterMethod = ReflectionUtility.findSetterMethod(entityClass, getterMethod); if (setterMethod == null) { String error = "No related 'set' method found for 'get' method " + getterMethod.getName() + " when processing entity type " + entityClass; logger.fatal(error); throw new RuntimeException(new NoSuchMethodException(error)); } // attempt to build the embeddable, if any, if not returns // false, and continues with default parameter builder if (!buildEmbeddable(entityClass, bindingPath, getterMethod, setterMethod, pbs, recursionFilterList)) { // default non embedded params ParameterBinder pb = buildParameterBinder(entityClass, bindingPath, getterMethod, setterMethod, parentGetterMethod, parentSetterMethod, index++, recursionFilterList); // MOVED TO buildParamaterBinder // // set the binding path if any. // String fieldName = ReflectionUtility.buildFieldName(getterMethod); // if (parentGetterMethod != null) { // pb.setBindingPath(bindingPath + "." + fieldName); // } else { // pb.setBindingPath(fieldName); // } // this may not be null, such as embedded objects, the parent // methods should be available. pb.setEntityClass(entityClass); // probably redundant, since its done // in buildParamterBinder(...) pb.setParentClass(parentClass); pb.setParentGetterMethod(parentGetterMethod); pb.setParentSetterMethod(parentSetterMethod); // add the parameter to the list of all parameters pbs.add(pb); } } } /** * Builds the embeddable parameter list. * * @param <T> * the generic type * @param parentClazz * the parent clazz * @param bindingPath * the binding path * @param getterMethod * the getter method * @param setterMethod * the setter method * @param pbs * the pbs * @param recursionFilterList * the recursion filter list * @return true, if successful */ @SuppressWarnings("unchecked") public <T> boolean buildEmbeddable(Class<T> parentClazz, String bindingPath, Method getterMethod, Method setterMethod, List<ParameterBinder> pbs, List<Class<?>> recursionFilterList) { Embedded embedded = ReflectionUtility.findAnnotation(getterMethod, Embedded.class); // TODO Should we process the class if the getter is not marked with // @Embedded? but the class itself is marked with @Embeddedable?? // Hibernate does it, but hibernate tries to be too COOL! if (embedded == null) { // check for embeddedable on the actual class type!! Class<?> returnType = getterMethod.getReturnType(); if (ReflectionUtility.hasAnyAnnotation(returnType, Embeddable.class)) { String error = "For consistency sake, you must define the " + Embedded.class + " annotation on the getter method " + getterMethod.getName() + " within entity type " + getterMethod.getDeclaringClass(); throw new AnnotationNotDefinedException(error); } } else { Class<?> embeddableClazz = getterMethod.getReturnType(); // [Java GENERICS] - Parameterized annotation class type "..." is // unchecked, probably should create Annotation[] { } ? if (!ReflectionUtility.hasAnyAnnotation(embeddableClazz, Embeddable.class)) { String error = "Class type " + embeddableClazz + " not marked with @" + Embeddable.class + " but used as an embedded parameter within " + parentClazz; logger.error(error); throw new RuntimeException(error); } // append the field name if (bindingPath.length() > 0) { bindingPath += "."; } String fieldName = ReflectionUtility.buildFieldName(getterMethod); bindingPath += fieldName; // recursively build the embedded objects as parameters build(embeddableClazz, bindingPath, parentClazz, getterMethod, setterMethod, pbs, recursionFilterList); return true; } return false; } /** * Throw basic class type exception on join column. If the JoinColumn data * type is set to a basic type, then we cannot use the {@link JoinColumn} or * {@link JoinColumns} annotation. This method checks for basic class types on * a {@link ParameterBinder} * * @param pb * the parameter binder to check against */ protected void throwBasicClassTypeExceptionOnJoinColumn(ParameterBinder pb) { // if its a basic type, then throw an exception, shouldn't be joining // non // entity types, it doesn't make sense to do it. if (ReflectionUtility.isBasicClass(pb.getTargetValueType())) { String error = "Cannot use a basic class such as Short, Integer, " + "String, Date, etc.. types for a join column pairing. " + "Please check the entity class " + pb.getEntityClass() + " and its associated method " + pb.getGetterMethod().getName(); logger.error(error); throw new RuntimeException(error); } } /** * Create a {@link DependencyJoin} and search the join entity for the primary * key column(s). Example, if we have a single {@link OneToOne} relationship, * or for most of the time, even a {@link ManyToOne} relationship with a * {@link JoinColumn} that uses the primary key. * * @param pb * the {@link ParameterBinder} * @param joinEntityDetail * the join entity detail, this is the entity class / details of the * join, for example the Parent Entity of a {@link ManyToOne} * relationship */ protected void doDependencyByPrimaryKey(ParameterBinder pb, EntityDetail<?> joinEntityDetail) { String columnName = pb.getJoinColumnName(); Class<?> entityClass = pb.getEntityClass(); Class<?> joinTargetClass = pb.getTargetValueType(); // Then we want to bind by the primary key. // set it up to the other join DependencyJoin dj = new DependencyJoin(); dj.setDependencyClass(joinTargetClass); // the dependency parameter should be the primary key at this point List<ParameterBinder> pbPrimaryKeys = joinEntityDetail.getPrimaryKeyParameterBinders(); // TODO should support multiple primary keys, however the JoinColumns // should be used?? // REMOVE THIS when support for composite-keys is available if (Comparison.isEmptyOrNull(pbPrimaryKeys)) { String error = "No primary keys found for entity type " + joinTargetClass; logger.error(error); throw new NullPointerException(error); } else if (pbPrimaryKeys.size() != 1) { String error = "Implementation does not currently support composite key join columns, please check entity type " + entityClass + " on column " + columnName + " and its associated inverse join."; logger.error(error); throw new RuntimeException(error); } ParameterBinder pbpk = pbPrimaryKeys.get(0); dj.setDependencyParameterBinder(pbpk); dj.setInverseJoin(false); // the dependency goes against the current param binder pb.setDependencyJoin(dj); } protected void doOneToManyDependencyJoinColumn(ParameterBinder pb, ParameterBinder pbJoinColumn) { Class<?> entityClass = pb.getEntityClass(); Class<?> joinTargetClass = pb.getTargetValueType(); // for example. // ParentEntity->List<Child> (joinColumn="PARENT_ID") // ChildEntity->Parent (joinColumn="PARENT_ID") // the query for ParentEntity->List<Child> would be // - select * from child where parent_id=2 // while all instances of ChildEntity.Parent = same instance of Parent // setup a dependency for the current entity to the join // column entity - the inverse class with the join column DependencyJoin dj = new DependencyJoin(); dj.setDependencyClass(joinTargetClass); dj.setDependencyParameterBinder(pbJoinColumn); dj.setInverseJoin(true); // the dependency goes against the current param binder pb.setDependencyJoin(dj); // setup the inverse join.. TODO is this logic correct? is this really // the inverse join or is the above the inverse join?? DependencyJoin djInverse = new DependencyJoin(); djInverse.setDependencyClass(entityClass); djInverse.setDependencyParameterBinder(pb); djInverse.setInverseJoin(true); pbJoinColumn.setDependencyJoin(djInverse); } protected void doSelfDependencyJoinByJoinColumn(ParameterBinder pbJoinColumn) { return; /* Class<?> entityClass = pbJoinColumn.getEntityClass(); if (Comparison.isNotEmpty(pbJoinColumn.getJoinColumn().referencedColumnName())) { throw new IllegalArgumentException("Cannot have an entity with a column that has a dependency on the same entity (itself) and use the referenced-column-name attribute. " + "technically you can, if the parameter is referenced column is unique, however, this jpa implementation only supports joining against the primary key. - for now. " + "I wouldn't know why the heck you would want to do that anyway!"); // TODO investigate into building a proper graph, this is a half ass job at building the dependency graph.. } EntityDetail<?> details = new EntityDetail<>(entityClass); // the dependency parameter should be the primary key at this point List<ParameterBinder> pbPrimaryKeys = details.getPrimaryKeyParameterBinders(); if (Comparison.isEmptyOrNull(pbPrimaryKeys)) { // this should probably never happen.. String error = "No primary keys found for entity type " + entityClass; logger.error(error); throw new NullPointerException(error); } else if (pbPrimaryKeys.size() != 1) { String error = "Implementation does not currently support composite key join columns, please check entity type " + entityClass + " on column " + pbJoinColumn.getColumnNameAnyJoinOrColumn() + " and its associated inverse join."; logger.error(error); throw new RuntimeException(error); } ParameterBinder pbpk = pbPrimaryKeys.get(0); DependencyJoin dj = new DependencyJoin(); dj.setDependencyClass(entityClass); dj.setDependencyParameterBinder(pbpk); dj.setInverseJoin(false); // the dependency goes against the current param binder pbJoinColumn.setDependencyJoin(dj);*/ } protected void doDepedencyByJoinColumn(ParameterBinder pb, ParameterBinder pbJoinColumn) { // Then we want to bind by the join column name on the inverse entity. if (pbJoinColumn.isOneToMany()) { doOneToManyDependencyJoinColumn(pb, pbJoinColumn); } else if (pbJoinColumn.isOneToOne()) { // TODO is this even legal? probably.. } else if (pbJoinColumn.isManyToOne()) { // TODO this is illegal?? } else if (pbJoinColumn.isManyToMany()) { // TODO need an interm-table?? } else { // TODO throw exception, no join found type found } } protected void doManyToOne(ParameterBinder pb, List<Class<?>> recursionFilterList) { Class<?> joinTargetClass = pb.getTargetValueType(); // Get the column name of the join column String columnName = pb.getJoinColumnName(); // Get the entity details for the given join column class type, for // example Parent.class EntityDetail<?> joinEntityDetail = null; // if the entity class is the same as the join target class, meaning its a recursive join to itself, probably by a parent id column, // then we need to get the entities values, otherwise the recursion, NOTE, this may cause an infinit loop, we should probably create // a graph of the entities, and check the loaded entities such that it does not continue to load data, incase the relationships are // setup such as e.g: a->b->c->b->c and so forth. // TODO check this logic very carefully. perhaps use a combination of target class and parameter binder as the join filter identifier, rather than just entity class type. joinEntityDetail = getEntityDetail(joinTargetClass, recursionFilterList); // If this returns, then we are good to process the join column, // otherwise // simply ignore it (probably filtered by recursionFilterList) if (joinEntityDetail != null) { // find the inverse join on the inverse entity ParameterBinder pbJoinColumn = joinEntityDetail.getParameterBinderByJoinColumn(columnName); boolean isDebug = logger.isDebugEnabled(); // if no join column was found then we probably want to join against the // primary key // @ManyToOne makes sense in this case. if (pbJoinColumn == null) { doDependencyByPrimaryKey(pb, joinEntityDetail); if (isDebug) { logger.debug("Entity " + pb.getEntityClass() + " on parameter " + pb.getBindingPath() + " is joining against table " + joinEntityDetail.getEntityClass() + " using its primary key"); } } else { doDepedencyByJoinColumn(pb, pbJoinColumn); if (isDebug) { logger.debug("Entity " + pb.getEntityClass() + " on parameter " + pb.getBindingPath() + " is joining against table " + joinEntityDetail.getTableName() + " on parameter " + pbJoinColumn.getBindingPath()); } } } else if (joinTargetClass.equals(pb.getEntityClass())) { // continue to build the dependency graph since the target class is actually the same as the entity class, // basically what this means is that, the entity refers to itself, and since we cannot continue to build // the entity details over-and-over again, we need to refer the parameter to itself. doSelfDependencyJoinByJoinColumn(pb); } } // // ManyToOne error?? // } else if (oneToMany != null) { // throw new NoJoinColumnMethodsDefinedException("No @" // + JoinColumn.class // + " defined on inverse entity of type " // + joinTargetClass // + " when searching for column name " // + columnName // + " please note it is NOT case sensitive!"); // } else { // } /** * Check for the {@link OneToMany} annotation, if it exists, then process the * ParameterBinder. This method may inevitably call a supporting method, which * in turn may call other methods that may inevitably call this method again. * * @param pb * the {@link ParameterBinder} in question. * @param recursionFilterList * the recursion filter list, a restriction filter, such that * dependency graphs don't go into a recursive loop. */ protected void doOneToMany(ParameterBinder pb, List<Class<?>> recursionFilterList) { if (ReflectionUtility.isCollectionClass(pb.getTargetValueType())) { Class<?> entityClass = pb.getEntityClass(); // if its a collection, we cannot determine its type, so we need to // search // for the inverse join column within another entity, usually done // when // building / iterating each entity! // TODO check for oneToMany?? // if the collection is marked with many to one, // this is incorrect!! TODO double check this behavour! if (pb.isManyToOne() || pb.isOneToOne()) { String error = "Trying to use a collection of entities bounded against " + pb.getColumnNameAnyJoinOrColumn() + " within entity " + entityClass + " with @" + JoinColumn.class + " and when the inversed join needs to be 1-n"; logger.error(error); throw new RuntimeException(error); } } } /** * Do column. checks whether the parameter has a {@link Column} annotation. * * @param pb * the {@link ParameterBinder} in question * @param recursionFilterList * the recursion filter list * @return true, if successful */ protected boolean doColumn(ParameterBinder pb, List<Class<?>> recursionFilterList) { if (pb.isColumn()) { return true; } return false; } /** * Do join column. checks whether the parameter has a {@link JoinColumn} * annotation. If so then processes the Dependency Graph for it. For example * if its a {@link OneToMany} relationship, it usually skips it and waits for * the inverse join {@link ManyToOne}. Note: this method will inevitably be * called recursively via supporting methods. * * @param pb * the {@link ParameterBinder} of the entity class in question. * @param recursionFilterList * the recursion filter list * @return true, if successful */ protected boolean doJoinColumn(ParameterBinder pb, List<Class<?>> recursionFilterList) { // if the binder is not a regular column, but a join column if (pb.isJoinColumn()) { throwBasicClassTypeExceptionOnJoinColumn(pb); if (pb.isManyToOne()) { doManyToOne(pb, recursionFilterList); } else if (pb.isOneToMany()) { doOneToMany(pb, recursionFilterList); } else if (pb.isOneToOne()) { } else if (pb.isManyToMany()) { } return true; } return false; } /** * Builds a single {@link ParameterBinder} for a given entity class method. * For example get methods that have been annotated with {@link Column} or * {@link JoinColumn} or {@link JoinColumns} * * @param <T> * the generic type of the entity class in question. * @param entityClass * the entity class as specified * @param getterMethod * the getter method of the parameter * @param setterMethod * the supporting setter method of the getter method of the parameter * @param index * the index of the parameter being processed * @param recursionFilterList * the recursion filter list, a restriction filter, such that * dependency graphs don't go into a recursive loop. * @return the {@link ParameterBinder} that was built for the given * getter/setter and entity. */ public <T> ParameterBinder buildParameterBinder(Class<T> entityClass, String bindingPath, Method getterMethod, Method setterMethod, Method parentGetterMethod, Method parentSetterMethod, int index, List<Class<?>> recursionFilterList) { ParameterBinder binder = new ParameterBinder(); // set the binding path if any. note this might be part of a recursive call // usually a join-column or embedded object String fieldName = ReflectionUtility.buildFieldName(getterMethod); if (parentGetterMethod != null) { binder.setBindingPath(bindingPath + "." + fieldName); } else { binder.setBindingPath(fieldName); } // build the binding name String bindingName = formatBindingName(ReflectionUtility.buildFieldName(getterMethod)); binder.setBindingIndex(index); binder.setBindingName(bindingName); binder.setGetterMethod(getterMethod); binder.setSetterMethod(setterMethod); binder.setEntityClass(entityClass); if (doColumn(binder, recursionFilterList)) { // TODO ?? } else if (doJoinColumn(binder, recursionFilterList)) { // TODO ?? //if (entityClass.equals(binder.getEntityClass())) { //} } /* * for (int j = 0; j < annotations.length; j++) { Annotation annotation = * annotations[j]; if (annotation instanceof Column) { Column column = * (Column) annotation; } // TODO check for max, min lengths!! else if * (annotation instanceof Id) { // Id id = (Id) annotation; * binder.setPrimaryKey(true); // } else if (annotation instanceof * SequenceGenerator) { // SequenceGenerator sequenceGenerator = // * (SequenceGenerator)annotation; } else if (annotation instanceof * Transient) { binder.setTransient(true); } } */ // check for setter method if not transient get method if (!binder.isTransient()) { if (binder.getSetterMethod() == null) { String error = "No set method for corresponding get method " + getterMethod.getName() + " found in entity class type " + entityClass; logger.error(error); throw new RuntimeException(new NoSuchMethodException(error)); } } return binder; } /** * Gets the DML operation for a given table entity class defined by the * {@link Table} and {@link Entity} annotations. * * @param <T> * the generic type of the table-entity * @param tableClass * the table-entity class * @param operationType * the operation type, such as insert, update, delete, merge, etc. * @return the DML operation used at the persistent layer. */ public <T> DMLOperation getDMLOperation(Class<T> tableClass, OperationType operationType) { if (getEntityPersistUtility() == null) { String error = "The data munipalation persist utility is not defined by any context. " + "Please make sure that when defining a " + EntityUtility.class + " you also define an implementation or sub-type of " + EntityPersistUtility.class; logger.error(error); throw new NullPointerException(); } EntityPersistUtility epu = getEntityPersistUtility(); return epu.getDMLOperation(tableClass, operationType); } /** * Sets the entity persist utility. * * @param entityPersistUtility * the new entity persist utility */ public void setEntityPersistUtility(EntityPersistUtility entityPersistUtility) { this.entityPersistUtility = entityPersistUtility; if (this.entityPersistUtility != null) { this.entityPersistUtility.setEntityUtility(this); } } /** * Gets the entity persist utility. * * @return the entity persist utility */ public EntityPersistUtility getEntityPersistUtility() { return entityPersistUtility; } /** * @return */ public List<String> getEntityPackages() { return entityPackages; } /** * @param entityPackages */ public void setEntityPackages(List<String> entityPackages) { this.entityPackages = entityPackages; } }