Java tutorial
package org.apache.ojb.broker.core; /* Copyright 2003-2005 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.sql.SQLException; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.ArrayUtils; import org.apache.ojb.broker.MtoNImplementor; import org.apache.ojb.broker.OJBRuntimeException; import org.apache.ojb.broker.PersistenceBrokerException; import org.apache.ojb.broker.PersistenceBrokerSQLException; import org.apache.ojb.broker.accesslayer.ResultSetAndStatement; import org.apache.ojb.broker.core.proxy.ProxyHelper; import org.apache.ojb.broker.metadata.ClassDescriptor; import org.apache.ojb.broker.metadata.CollectionDescriptor; import org.apache.ojb.broker.metadata.DescriptorRepository; import org.apache.ojb.broker.metadata.FieldDescriptor; import org.apache.ojb.broker.metadata.JdbcType; import org.apache.ojb.broker.query.Query; import org.apache.ojb.broker.util.logging.Logger; import org.apache.ojb.broker.util.logging.LoggerFactory; /** * Manage all stuff related to non-decomposed M:N association. * * @author <a href="mailto:thma@apache.org">Thomas Mahler<a> * @author <a href="mailto:leandro@ibnetwork.com.br">Leandro Rodrigo Saad Cruz<a> * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird<a> * @author <a href="mailto:jbraeuchi@hotmail.com">Jakob Braeuchi</a> * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a> * @version $Id: MtoNBroker.java,v 1.10.2.9 2005/12/21 22:25:00 tomdz Exp $ */ public class MtoNBroker { private Logger log = LoggerFactory.getLogger(MtoNBroker.class); private PersistenceBrokerImpl pb; /** * Used to store {@link GenericObject} while transaction running, used as * workaround for m:n insert problem. * TODO: find better solution for m:n handling */ private List tempObjects = new ArrayList(); public MtoNBroker(final PersistenceBrokerImpl broker) { this.pb = broker; } public void reset() { tempObjects.clear(); } /** * Stores new values of a M:N association in a indirection table. * * @param cod The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} for the m:n relation * @param realObject The real object * @param otherObj The referenced object * @param mnKeys The all {@link org.apache.ojb.broker.core.MtoNBroker.Key} matching the real object */ public void storeMtoNImplementor(CollectionDescriptor cod, Object realObject, Object otherObj, Collection mnKeys) { ClassDescriptor cld = pb.getDescriptorRepository().getDescriptorFor(realObject.getClass()); ValueContainer[] pkValues = pb.serviceBrokerHelper().getKeyValues(cld, realObject); String[] pkColumns = cod.getFksToThisClass(); ClassDescriptor otherCld = pb.getDescriptorRepository() .getDescriptorFor(ProxyHelper.getRealClass(otherObj)); ValueContainer[] otherPkValues = pb.serviceBrokerHelper().getKeyValues(otherCld, otherObj); String[] otherPkColumns = cod.getFksToItemClass(); String table = cod.getIndirectionTable(); MtoNBroker.Key key = new MtoNBroker.Key(otherPkValues); if (mnKeys.contains(key)) { return; } /* fix for OJB-76, composite M & N keys that have some fields common find the "shared" indirection table columns, values and remove these from m- or n- side */ for (int i = 0; i < otherPkColumns.length; i++) { int index = ArrayUtils.indexOf(pkColumns, otherPkColumns[i]); if (index != -1) { // shared indirection table column found, remove this column from one side pkColumns = (String[]) ArrayUtils.remove(pkColumns, index); // remove duplicate value too pkValues = (ValueContainer[]) ArrayUtils.remove(pkValues, index); } } String[] cols = mergeColumns(pkColumns, otherPkColumns); String insertStmt = pb.serviceSqlGenerator().getInsertMNStatement(table, pkColumns, otherPkColumns); ValueContainer[] values = mergeContainer(pkValues, otherPkValues); GenericObject gObj = new GenericObject(table, cols, values); if (!tempObjects.contains(gObj)) { pb.serviceJdbcAccess().executeUpdateSQL(insertStmt, cld, pkValues, otherPkValues); tempObjects.add(gObj); } } /** * get a Collection of Keys of already existing m:n rows * * @param cod * @param obj * @return Collection of Key */ public List getMtoNImplementor(CollectionDescriptor cod, Object obj) { ResultSetAndStatement rs = null; ArrayList result = new ArrayList(); ClassDescriptor cld = pb.getDescriptorRepository().getDescriptorFor(obj.getClass()); ValueContainer[] pkValues = pb.serviceBrokerHelper().getKeyValues(cld, obj); String[] pkColumns = cod.getFksToThisClass(); String[] fkColumns = cod.getFksToItemClass(); String table = cod.getIndirectionTable(); String selectStmt = pb.serviceSqlGenerator().getSelectMNStatement(table, fkColumns, pkColumns); ClassDescriptor itemCLD = pb.getDescriptorRepository().getDescriptorFor(cod.getItemClass()); Collection extents = pb.getDescriptorRepository().getAllConcreteSubclassDescriptors(itemCLD); if (extents.size() > 0) { itemCLD = (ClassDescriptor) extents.iterator().next(); } FieldDescriptor[] itemClassPKFields = itemCLD.getPkFields(); if (itemClassPKFields.length != fkColumns.length) { throw new PersistenceBrokerException("All pk fields of the element-class need to" + " be declared in the indirection table. Element class is " + itemCLD.getClassNameOfObject() + " with " + itemClassPKFields.length + " pk-fields." + " Declared 'fk-pointing-to-element-class' elements in collection-descriptor are" + fkColumns.length); } try { rs = pb.serviceJdbcAccess().executeSQL(selectStmt, cld, pkValues, Query.NOT_SCROLLABLE); while (rs.m_rs.next()) { ValueContainer[] row = new ValueContainer[fkColumns.length]; for (int i = 0; i < row.length; i++) { row[i] = new ValueContainer(rs.m_rs.getObject(i + 1), itemClassPKFields[i].getJdbcType()); } result.add(new MtoNBroker.Key(row)); } } catch (PersistenceBrokerException e) { throw e; } catch (SQLException e) { throw new PersistenceBrokerSQLException(e); } finally { if (rs != null) rs.close(); } return result; } /** * delete all rows from m:n table belonging to obj * * @param cod * @param obj */ public void deleteMtoNImplementor(CollectionDescriptor cod, Object obj) { ClassDescriptor cld = pb.getDescriptorRepository().getDescriptorFor(obj.getClass()); ValueContainer[] pkValues = pb.serviceBrokerHelper().getKeyValues(cld, obj); String[] pkColumns = cod.getFksToThisClass(); String table = cod.getIndirectionTable(); String deleteStmt = pb.serviceSqlGenerator().getDeleteMNStatement(table, pkColumns, null); pb.serviceJdbcAccess().executeUpdateSQL(deleteStmt, cld, pkValues, null); } /** * deletes all rows from m:n table that are not used in relatedObjects * * @param cod * @param obj * @param collectionIterator * @param mnKeys */ public void deleteMtoNImplementor(CollectionDescriptor cod, Object obj, Iterator collectionIterator, Collection mnKeys) { if (mnKeys.isEmpty() || collectionIterator == null) { return; } List workList = new ArrayList(mnKeys); MtoNBroker.Key relatedObjKeys; ClassDescriptor relatedCld = pb.getDescriptorRepository().getDescriptorFor(cod.getItemClass()); Object relatedObj; // remove keys of relatedObject from the existing m:n rows in workList while (collectionIterator.hasNext()) { relatedObj = collectionIterator.next(); relatedObjKeys = new MtoNBroker.Key( pb.serviceBrokerHelper().getKeyValues(relatedCld, relatedObj, true)); workList.remove(relatedObjKeys); } // delete all remaining keys in workList ClassDescriptor cld = pb.getDescriptorRepository().getDescriptorFor(obj.getClass()); ValueContainer[] pkValues = pb.serviceBrokerHelper().getKeyValues(cld, obj); String[] pkColumns = cod.getFksToThisClass(); String[] fkColumns = cod.getFksToItemClass(); String table = cod.getIndirectionTable(); String deleteStmt; ValueContainer[] fkValues; Iterator iter = workList.iterator(); while (iter.hasNext()) { fkValues = ((MtoNBroker.Key) iter.next()).m_containers; deleteStmt = pb.serviceSqlGenerator().getDeleteMNStatement(table, pkColumns, fkColumns); pb.serviceJdbcAccess().executeUpdateSQL(deleteStmt, cld, pkValues, fkValues); } } /** * @param m2n */ public void storeMtoNImplementor(MtoNImplementor m2n) { if (log.isDebugEnabled()) log.debug("Storing M2N implementor [" + m2n + "]"); insertOrDeleteMtoNImplementor(m2n, true); } /** * @param m2n */ public void deleteMtoNImplementor(MtoNImplementor m2n) { if (log.isDebugEnabled()) log.debug("Deleting M2N implementor [" + m2n + "]"); insertOrDeleteMtoNImplementor(m2n, false); } /** * @see org.apache.ojb.broker.PersistenceBroker#deleteMtoNImplementor */ private void insertOrDeleteMtoNImplementor(MtoNImplementor m2nImpl, boolean insert) throws PersistenceBrokerException { //look for a collection descriptor on left such as left.element-class-ref='right' DescriptorRepository dr = pb.getDescriptorRepository(); Object leftObject = m2nImpl.getLeftObject(); Class leftClass = m2nImpl.getLeftClass(); Object rightObject = m2nImpl.getRightObject(); Class rightClass = m2nImpl.getRightClass(); //are written per class, maybe referencing abstract classes or interfaces //so let's look for collection descriptors on the left class and try to // handle extents on teh right class ClassDescriptor leftCld = dr.getDescriptorFor(leftClass); ClassDescriptor rightCld = dr.getDescriptorFor(rightClass); //Vector leftColds = leftCld.getCollectionDescriptors(); CollectionDescriptor wanted = m2nImpl.getLeftDescriptor(); if (leftObject == null || rightObject == null) { //TODO: to be implemented, must change MtoNImplementor //deleteMtoNImplementor(wanted,leftObject) || deleteMtoNImplementor(wanted,rightObject) log.error("Can't handle MtoNImplementor in correct way, found a 'null' object"); } else { //delete only one row ValueContainer[] leftPkValues = pb.serviceBrokerHelper().getKeyValues(leftCld, leftObject); ValueContainer[] rightPkValues = pb.serviceBrokerHelper().getKeyValues(rightCld, rightObject); String[] pkLeftColumns = wanted.getFksToThisClass(); String[] pkRightColumns = wanted.getFksToItemClass(); String table = wanted.getIndirectionTable(); if (table == null) throw new PersistenceBrokerException("Can't remove MtoN implementor without an indirection table"); String stmt; String[] cols = mergeColumns(pkLeftColumns, pkRightColumns); ValueContainer[] values = mergeContainer(leftPkValues, rightPkValues); if (insert) { stmt = pb.serviceSqlGenerator().getInsertMNStatement(table, pkLeftColumns, pkRightColumns); GenericObject gObj = new GenericObject(table, cols, values); if (!tempObjects.contains(gObj)) { pb.serviceJdbcAccess().executeUpdateSQL(stmt, leftCld, leftPkValues, rightPkValues); tempObjects.add(gObj); } } else { stmt = pb.serviceSqlGenerator().getDeleteMNStatement(table, pkLeftColumns, pkRightColumns); pb.serviceJdbcAccess().executeUpdateSQL(stmt, leftCld, leftPkValues, rightPkValues); } } } private String[] mergeColumns(String[] first, String[] second) { String[] cols = new String[first.length + second.length]; System.arraycopy(first, 0, cols, 0, first.length); System.arraycopy(second, 0, cols, first.length, second.length); return cols; } private ValueContainer[] mergeContainer(ValueContainer[] first, ValueContainer[] second) { ValueContainer[] values = new ValueContainer[first.length + second.length]; System.arraycopy(first, 0, values, 0, first.length); System.arraycopy(second, 0, values, first.length, second.length); return values; } // ************************************************************************ // inner class // ************************************************************************ /** * This is a helper class to model a Key of an Object */ private static final class Key { final ValueContainer[] m_containers; Key(final ValueContainer[] containers) { m_containers = new ValueContainer[containers.length]; for (int i = 0; i < containers.length; i++) { Object value = containers[i].getValue(); JdbcType type = containers[i].getJdbcType(); // BRJ: // convert all Numbers to Long to simplify equals // Long(100) is not equal to Integer(100) // // could lead to problems when Floats are used as key // converting to String could be a better alternative if (value instanceof Number) { value = new Long(((Number) value).longValue()); } m_containers[i] = new ValueContainer(value, type); } } public boolean equals(Object other) { if (other == this) { return true; } if (!(other instanceof Key)) { return false; } Key otherKey = (Key) other; EqualsBuilder eb = new EqualsBuilder(); eb.append(m_containers, otherKey.m_containers); return eb.isEquals(); } public int hashCode() { HashCodeBuilder hb = new HashCodeBuilder(); hb.append(m_containers); return hb.toHashCode(); } } // ************************************************************************ // inner class // ************************************************************************ private static final class GenericObject { private String tablename; private String[] columnNames; private ValueContainer[] values; public GenericObject(String tablename, String[] columnNames, ValueContainer[] values) { this.tablename = tablename; this.columnNames = columnNames; this.values = values; if (values != null && columnNames.length != values.length) { throw new OJBRuntimeException("Column name array and value array have NOT same length"); } } public boolean equals(Object obj) { if (this == obj) { return true; } boolean result = false; if (obj instanceof GenericObject) { GenericObject other = (GenericObject) obj; result = (tablename.equalsIgnoreCase(other.tablename) && (columnNames != null) && (other.columnNames != null) && (columnNames.length == other.columnNames.length)); if (result) { for (int i = 0; i < columnNames.length; i++) { int otherIndex = other.indexForColumn(columnNames[i]); if (otherIndex < 0) { result = false; break; } result = values[i].equals(other.values[otherIndex]); if (!result) break; } } } return result; } int indexForColumn(String name) { int result = -1; for (int i = 0; i < columnNames.length; i++) { if (columnNames[i].equals(name)) { result = i; break; } } return result; } public int hashCode() { return super.hashCode(); } public ValueContainer getValueFor(String columnName) { try { return values[indexForColumn(columnName)]; } catch (Exception e) { throw new OJBRuntimeException("Can't find value for column " + columnName + (indexForColumn(columnName) < 0 ? ". Column name was not found" : ""), e); } } public String getTablename() { return tablename; } public String[] getColumnNames() { return columnNames; } public ValueContainer[] getValues() { return values; } public void setValues(ValueContainer[] values) { this.values = values; } public String toString() { return new ToStringBuilder(this).append("tableName", tablename).append("columnNames", columnNames) .append("values", values).toString(); } } }