/******************************************************************************* * Copyright (c) 2010 Robert "Unlogic" Olofsson (unlogic@unlogic.se). * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v3 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl-3.0-standalone.html ******************************************************************************/ package se.unlogic.standardutils.dao; import se.unlogic.standardutils.dao.annotations.DAOManaged; import se.unlogic.standardutils.dao.annotations.Key; import se.unlogic.standardutils.dao.annotations.ManyToMany; import se.unlogic.standardutils.dao.querys.UpdateQuery; import se.unlogic.standardutils.reflection.ReflectionUtils; import se.unlogic.standardutils.string.StringUtils; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class DefaultManyToManyRelation<LocalType,RemoteType> implements ManyToManyRelation<LocalType, RemoteType> { private final Field field; private final Field localKeyField; private final Field remoteKeyField; private final String linkTable; private final String localLinkTableColumnName; private final String remoteKeyColumnName; private final String remoteLinkTableColumnName; private String linkTableLinkSQL; private String linkTableDeleteSQL; private String linkTableInsertSQL; private Column<LocalType,?> localColumn; private Column<RemoteType, ?> remoteColumn; private final AnnotatedDAOFactory daoFactory; private AnnotatedDAO<RemoteType> annotatedDAO; private final Class<LocalType> beanClass; private final Class<RemoteType> remoteClass; private boolean initialized; public DefaultManyToManyRelation(Class<LocalType> beanClass, Class<RemoteType> remoteClass, Field field, AnnotatedDAOFactory daoFactory, DAOManaged daoManaged) { super(); this.beanClass = beanClass; this.remoteClass = remoteClass; this.field = field; this.daoFactory = daoFactory; //Validate linkTable name ManyToMany localAnnotation = field.getAnnotation(ManyToMany.class); this.linkTable = localAnnotation.linkTable(); //Get remote field Field matchingRemoteField = null; Field[] fields = remoteClass.getDeclaredFields(); for(Field remoteField : fields){ if(ReflectionUtils.getGenericlyTypeCount(remoteField) == 1 && ReflectionUtils.getGenericType(remoteField).equals(beanClass) && remoteField.isAnnotationPresent(DAOManaged.class) && remoteField.isAnnotationPresent(ManyToMany.class) && remoteField.getAnnotation(ManyToMany.class).linkTable().equals(linkTable)){ matchingRemoteField = remoteField; break; } } if(matchingRemoteField == null){ throw new RuntimeException("Unable to to find corresponding @ManyToMany field in " + remoteClass + " while parsing field " + field.getName() + " in " + beanClass); } //Get remote annotation ManyToMany remoteAnnotation = matchingRemoteField.getAnnotation(ManyToMany.class); //Get local key field this.localKeyField = getKeyField(localAnnotation, beanClass, field); //Get local localColumn name this.localLinkTableColumnName = getColumnName(daoManaged, localKeyField); //Get remote key field remoteKeyField = getKeyField(remoteAnnotation, remoteClass, matchingRemoteField); //Get remote localColumn name this.remoteLinkTableColumnName = getColumnName(remoteKeyField.getAnnotation(DAOManaged.class), remoteKeyField); String remoteColumnName = remoteKeyField.getAnnotation(DAOManaged.class).columnName(); //Get remote table localColumn name if(!StringUtils.isEmpty(remoteColumnName)){ this.remoteKeyColumnName = remoteColumnName; }else{ this.remoteKeyColumnName = remoteKeyField.getName(); } this.linkTableDeleteSQL = "DELETE FROM " + linkTable + " WHERE " + localLinkTableColumnName + " = ?"; this.linkTableInsertSQL = "INSERT INTO " + linkTable + " (" + localLinkTableColumnName + "," + remoteLinkTableColumnName + ") VALUES (?,?)"; } /* (non-Javadoc) * @see se.unlogic.utils.dao.ManyToManyRelation#setValue(LocalType, java.sql.Connection, java.lang.reflect.Field[]) */ public void getRemoteValue(LocalType bean, Connection connection, RelationQuery relationQuery) throws SQLException{ try { if(!initialized){ init(); } CustomQueryParameter<LocalType> queryParameter = new CustomQueryParameter<LocalType>(localColumn, bean); if(queryParameter.getParamValue() != null){ field.set(bean, annotatedDAO.getAll(linkTableLinkSQL, queryParameter, connection, relationQuery)); } } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } /* (non-Javadoc) * @see se.unlogic.utils.dao.ManyToManyRelation#add(LocalType, java.sql.Connection, java.lang.reflect.Field[]) */ @SuppressWarnings("unchecked") public void add(LocalType bean, Connection connection, RelationQuery relationQuery) throws SQLException{ //TODO cascading add like OneToMany relations try { if(!initialized){ init(); } //Check if there are any relations to set List<RemoteType> remoteBeans = (List<RemoteType>) field.get(bean); if(remoteBeans == null){ return; } for(RemoteType remoteBean : remoteBeans){ //Set new relations UpdateQuery insertQuery; insertQuery = new UpdateQuery(connection, false, linkTableInsertSQL); setQueryParameter(insertQuery, this.localColumn, bean, 1); setQueryParameter(insertQuery, this.remoteColumn, remoteBean, 2); insertQuery.executeUpdate(); } } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } /* (non-Javadoc) * @see se.unlogic.utils.dao.ManyToManyRelation#update(LocalType, java.sql.Connection, java.lang.reflect.Field[]) */ @SuppressWarnings("unchecked") public void update(LocalType bean, Connection connection, RelationQuery relationQuery) throws SQLException{ //TODO cascading add like OneToMany relations try { if(!initialized){ init(); } //Clear old relations UpdateQuery deleteQuery; deleteQuery = new UpdateQuery(connection, false, linkTableDeleteSQL); setQueryParameter(deleteQuery, this.localColumn, bean, 1); deleteQuery.executeUpdate(); //Check if there are any new relations to set List<RemoteType> remoteBeans = (List<RemoteType>) field.get(bean); if(remoteBeans == null){ return; } for(RemoteType remoteBean : remoteBeans){ //Set new relations UpdateQuery insertQuery; insertQuery = new UpdateQuery(connection, false, linkTableInsertSQL); setQueryParameter(insertQuery, this.localColumn, bean, 1); setQueryParameter(insertQuery, this.remoteColumn, remoteBean, 2); insertQuery.executeUpdate(); } } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } private void init() { if(annotatedDAO == null){ annotatedDAO = this.daoFactory.getDAO(remoteClass); } if(this.linkTableLinkSQL == null){ this.linkTableLinkSQL = "SELECT " + annotatedDAO.getTableName() + ".* FROM " + annotatedDAO.getTableName() + " INNER JOIN " + linkTable + " ON (" + annotatedDAO.getTableName() + "." + remoteKeyColumnName + "=" + linkTable + "." + remoteLinkTableColumnName + ") WHERE " + linkTable + "." + localLinkTableColumnName + " = ?"; } if(this.localColumn == null){ this.localColumn = this.daoFactory.getDAO(beanClass).getColumn(this.localKeyField); } if(this.remoteColumn == null){ this.remoteColumn = this.annotatedDAO.getColumn(remoteKeyField); } this.initialized = true; } private static <Type> void setQueryParameter(UpdateQuery query, Column<Type,?> column, Type bean, int index) throws SQLException{ if(column.getQueryParameterPopulator() != null){ column.getQueryParameterPopulator().populate(query, index, column.getBeanValue(bean)); }else{ try { column.getQueryMethod().invoke(query, index, column.getBeanValue(bean)); } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } } public static Field getKeyField(ManyToMany manyToManyAnnotation, Class<?> clazz, Field annotatedField){ if(!StringUtils.isEmpty(manyToManyAnnotation.keyField())){ try { Field keyField = clazz.getDeclaredField(manyToManyAnnotation.keyField()); DAOManaged keyDAOPopulate = keyField.getAnnotation(DAOManaged.class); if(keyDAOPopulate == null){ throw new RuntimeException("Specified keyField " + manyToManyAnnotation.keyField() + " for @ManyToMany annotation for field " + annotatedField.getName() + " in " + clazz + " is missing the @DAOManaged annotation"); } return keyField; } catch (SecurityException e) { throw new RuntimeException("Unable to find specified keyField " + manyToManyAnnotation.keyField() + " for @ManyToMany annotation for field " + annotatedField.getName() + " in " + clazz); } catch (NoSuchFieldException e) { throw new RuntimeException("Unable to find specified keyField " + manyToManyAnnotation.keyField() + " for @ManyToMany annotation for field " + annotatedField.getName() + " in " + clazz); } }else{ ArrayList<Field> keyFields = getKeyFields(clazz); if(keyFields.size() == 0){ throw new RuntimeException("Unable to find any @Key annotated fields in " + clazz); }else if(keyFields.size() > 1){ throw new RuntimeException("keyField needs to be specified for @ManyToMany annotated field " + annotatedField.getName() + " in " + clazz + " since the class contains multiple @Key annotated fields"); } return keyFields.get(0); } } public static ArrayList<Field> getKeyFields(Class<?> clazz){ ArrayList<Field> keyFields = new ArrayList<Field>(); Field[] fields = clazz.getDeclaredFields(); for(Field field : fields){ if(field.isAnnotationPresent(Key.class) && field.isAnnotationPresent(DAOManaged.class)){ keyFields.add(field); } } return keyFields; } private static String getColumnName(DAOManaged manyToManyDAOPopulate, Field keyField){ String keyColumnName; if(!StringUtils.isEmpty(manyToManyDAOPopulate.columnName())){ return manyToManyDAOPopulate.columnName(); }else if(!StringUtils.isEmpty(keyColumnName = keyField.getAnnotation(DAOManaged.class).columnName())){ return keyColumnName; }else{ return keyField.getName(); } } public static <LT,RT> ManyToManyRelation<LT, RT> getGenericInstance(Class<LT> beanClass, Class<RT> remoteClass, Field field, AnnotatedDAOFactory daoFactory, DAOManaged daoManaged){ return new DefaultManyToManyRelation<LT,RT>(beanClass,remoteClass,field,daoFactory,daoManaged); } }