Coverage Report - org.apache.ojb.broker.core.MtoNBroker
 
Classes in this File Line Coverage Branch Coverage Complexity
MtoNBroker
N/A
N/A
3.208
MtoNBroker$GenericObject
N/A
N/A
3.208
MtoNBroker$Key
N/A
N/A
3.208
 
 1  
 package org.apache.ojb.broker.core;
 2  
 
 3  
 /* Copyright 2003-2005 The Apache Software Foundation
 4  
  *
 5  
  * Licensed under the Apache License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * You may obtain a copy of the License at
 8  
  *
 9  
  *     http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 
 18  
 import java.util.ArrayList;
 19  
 import java.util.Collection;
 20  
 import java.util.Iterator;
 21  
 import java.util.List;
 22  
 import java.sql.SQLException;
 23  
 
 24  
 import org.apache.commons.lang.builder.EqualsBuilder;
 25  
 import org.apache.commons.lang.builder.HashCodeBuilder;
 26  
 import org.apache.commons.lang.builder.ToStringBuilder;
 27  
 import org.apache.commons.lang.ArrayUtils;
 28  
 import org.apache.ojb.broker.MtoNImplementor;
 29  
 import org.apache.ojb.broker.OJBRuntimeException;
 30  
 import org.apache.ojb.broker.PersistenceBrokerException;
 31  
 import org.apache.ojb.broker.PersistenceBrokerSQLException;
 32  
 import org.apache.ojb.broker.accesslayer.ResultSetAndStatement;
 33  
 import org.apache.ojb.broker.core.proxy.ProxyHelper;
 34  
 import org.apache.ojb.broker.metadata.ClassDescriptor;
 35  
 import org.apache.ojb.broker.metadata.CollectionDescriptor;
 36  
 import org.apache.ojb.broker.metadata.DescriptorRepository;
 37  
 import org.apache.ojb.broker.metadata.FieldDescriptor;
 38  
 import org.apache.ojb.broker.metadata.JdbcType;
 39  
 import org.apache.ojb.broker.query.Query;
 40  
 import org.apache.ojb.broker.util.logging.Logger;
 41  
 import org.apache.ojb.broker.util.logging.LoggerFactory;
 42  
 
 43  
 /**
 44  
  * Manage all stuff related to non-decomposed M:N association.
 45  
  *
 46  
  * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
 47  
  * @author <a href="mailto:leandro@ibnetwork.com.br">Leandro Rodrigo Saad Cruz<a>
 48  
  * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird<a>
 49  
  * @author <a href="mailto:jbraeuchi@hotmail.com">Jakob Braeuchi</a>
 50  
  * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
 51  
  * @version $Id: MtoNBroker.java,v 1.1 2007-08-24 22:17:35 ewestfal Exp $
 52  
  */
 53  
 public class MtoNBroker
 54  
 {
 55  
     private Logger log = LoggerFactory.getLogger(MtoNBroker.class);
 56  
 
 57  
     private PersistenceBrokerImpl pb;
 58  
     /**
 59  
      * Used to store {@link GenericObject} while transaction running, used as
 60  
      * workaround for m:n insert problem.
 61  
      * TODO: find better solution for m:n handling
 62  
      */
 63  
     private List tempObjects = new ArrayList();
 64  
 
 65  
     public MtoNBroker(final PersistenceBrokerImpl broker)
 66  
     {
 67  
         this.pb = broker;
 68  
     }
 69  
 
 70  
     public void reset()
 71  
     {
 72  
         tempObjects.clear();
 73  
     }
 74  
 
 75  
     /**
 76  
      * Stores new values of a M:N association in a indirection table.
 77  
      *
 78  
      * @param cod        The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} for the m:n relation
 79  
      * @param realObject The real object
 80  
      * @param otherObj   The referenced object
 81  
      * @param mnKeys     The all {@link org.apache.ojb.broker.core.MtoNBroker.Key} matching the real object
 82  
      */
 83  
     public void storeMtoNImplementor(CollectionDescriptor cod, Object realObject, Object otherObj, Collection mnKeys)
 84  
     {
 85  
         ClassDescriptor cld = pb.getDescriptorRepository().getDescriptorFor(realObject.getClass());
 86  
         ValueContainer[] pkValues = pb.serviceBrokerHelper().getKeyValues(cld, realObject);
 87  
         String[] pkColumns = cod.getFksToThisClass();
 88  
 
 89  
         ClassDescriptor otherCld = pb.getDescriptorRepository().getDescriptorFor(ProxyHelper.getRealClass(otherObj));
 90  
         ValueContainer[] otherPkValues = pb.serviceBrokerHelper().getKeyValues(otherCld, otherObj);
 91  
 
 92  
         String[] otherPkColumns = cod.getFksToItemClass();
 93  
         String table = cod.getIndirectionTable();
 94  
         MtoNBroker.Key key = new MtoNBroker.Key(otherPkValues);
 95  
 
 96  
         if(mnKeys.contains(key))
 97  
         {
 98  
             return;
 99  
         }
 100  
 
 101  
         /*
 102  
         fix for OJB-76, composite M & N keys that have some fields common
 103  
         find the "shared" indirection table columns, values and remove these from m- or n- side
 104  
         */
 105  
         for(int i = 0; i < otherPkColumns.length; i++)
 106  
         {
 107  
             int index = ArrayUtils.indexOf(pkColumns, otherPkColumns[i]);
 108  
             if(index != -1)
 109  
             {
 110  
                 // shared indirection table column found, remove this column from one side
 111  
                 pkColumns = (String[]) ArrayUtils.remove(pkColumns, index);
 112  
                 // remove duplicate value too
 113  
                 pkValues = (ValueContainer[]) ArrayUtils.remove(pkValues, index);
 114  
             }
 115  
         }
 116  
 
 117  
         String[] cols = mergeColumns(pkColumns, otherPkColumns);
 118  
         String insertStmt = pb.serviceSqlGenerator().getInsertMNStatement(table, pkColumns, otherPkColumns);
 119  
         ValueContainer[] values = mergeContainer(pkValues, otherPkValues);
 120  
         GenericObject gObj = new GenericObject(table, cols, values);
 121  
         if(! tempObjects.contains(gObj))
 122  
         {
 123  
             pb.serviceJdbcAccess().executeUpdateSQL(insertStmt, cld, pkValues, otherPkValues);
 124  
             tempObjects.add(gObj);
 125  
         }
 126  
     }
 127  
 
 128  
     /**
 129  
      * get a Collection of Keys of already existing m:n rows
 130  
      *
 131  
      * @param cod
 132  
      * @param obj
 133  
      * @return Collection of Key
 134  
      */
 135  
     public List getMtoNImplementor(CollectionDescriptor cod, Object obj)
 136  
     {
 137  
         ResultSetAndStatement rs = null;
 138  
         ArrayList result = new ArrayList();
 139  
         ClassDescriptor cld = pb.getDescriptorRepository().getDescriptorFor(obj.getClass());
 140  
         ValueContainer[] pkValues = pb.serviceBrokerHelper().getKeyValues(cld, obj);
 141  
         String[] pkColumns = cod.getFksToThisClass();
 142  
         String[] fkColumns = cod.getFksToItemClass();
 143  
         String table = cod.getIndirectionTable();
 144  
 
 145  
         String selectStmt = pb.serviceSqlGenerator().getSelectMNStatement(table, fkColumns, pkColumns);
 146  
 
 147  
         ClassDescriptor itemCLD = pb.getDescriptorRepository().getDescriptorFor(cod.getItemClass());
 148  
         Collection extents = pb.getDescriptorRepository().getAllConcreteSubclassDescriptors(itemCLD);
 149  
         if(extents.size() > 0)
 150  
         {
 151  
             itemCLD = (ClassDescriptor) extents.iterator().next();
 152  
         }
 153  
         FieldDescriptor[] itemClassPKFields = itemCLD.getPkFields();
 154  
         if(itemClassPKFields.length != fkColumns.length)
 155  
         {
 156  
             throw new PersistenceBrokerException("All pk fields of the element-class need to" +
 157  
                     " be declared in the indirection table. Element class is "
 158  
                     + itemCLD.getClassNameOfObject() + " with " + itemClassPKFields.length + " pk-fields." +
 159  
                     " Declared 'fk-pointing-to-element-class' elements in collection-descriptor are"
 160  
                     + fkColumns.length);
 161  
         }
 162  
         try
 163  
         {
 164  
             rs = pb.serviceJdbcAccess().executeSQL(selectStmt, cld, pkValues, Query.NOT_SCROLLABLE);
 165  
             while(rs.m_rs.next())
 166  
             {
 167  
                 ValueContainer[] row = new ValueContainer[fkColumns.length];
 168  
                 for(int i = 0; i < row.length; i++)
 169  
                 {
 170  
                     row[i] = new ValueContainer(rs.m_rs.getObject(i + 1), itemClassPKFields[i].getJdbcType());
 171  
                 }
 172  
                 result.add(new MtoNBroker.Key(row));
 173  
             }
 174  
         }
 175  
         catch(PersistenceBrokerException e)
 176  
         {
 177  
             throw e;
 178  
         }
 179  
         catch(SQLException e)
 180  
         {
 181  
             throw new PersistenceBrokerSQLException(e);
 182  
         }
 183  
         finally
 184  
         {
 185  
             if(rs != null) rs.close();
 186  
         }
 187  
         return result;
 188  
     }
 189  
 
 190  
     /**
 191  
      * delete all rows from m:n table belonging to obj
 192  
      *
 193  
      * @param cod
 194  
      * @param obj
 195  
      */
 196  
     public void deleteMtoNImplementor(CollectionDescriptor cod, Object obj)
 197  
     {
 198  
         ClassDescriptor cld = pb.getDescriptorRepository().getDescriptorFor(obj.getClass());
 199  
         ValueContainer[] pkValues = pb.serviceBrokerHelper().getKeyValues(cld, obj);
 200  
         String[] pkColumns = cod.getFksToThisClass();
 201  
         String table = cod.getIndirectionTable();
 202  
         String deleteStmt = pb.serviceSqlGenerator().getDeleteMNStatement(table, pkColumns, null);
 203  
         pb.serviceJdbcAccess().executeUpdateSQL(deleteStmt, cld, pkValues, null);
 204  
     }
 205  
 
 206  
     /**
 207  
      * deletes all rows from m:n table that are not used in relatedObjects
 208  
      *
 209  
      * @param cod
 210  
      * @param obj
 211  
      * @param collectionIterator
 212  
      * @param mnKeys
 213  
      */
 214  
     public void deleteMtoNImplementor(CollectionDescriptor cod, Object obj, Iterator collectionIterator, Collection mnKeys)
 215  
     {
 216  
         if(mnKeys.isEmpty() || collectionIterator == null)
 217  
         {
 218  
             return;
 219  
         }
 220  
         List workList = new ArrayList(mnKeys);
 221  
         MtoNBroker.Key relatedObjKeys;
 222  
         ClassDescriptor relatedCld = pb.getDescriptorRepository().getDescriptorFor(cod.getItemClass());
 223  
         Object relatedObj;
 224  
 
 225  
         // remove keys of relatedObject from the existing m:n rows in workList
 226  
         while(collectionIterator.hasNext())
 227  
         {
 228  
             relatedObj = collectionIterator.next();
 229  
             relatedObjKeys = new MtoNBroker.Key(pb.serviceBrokerHelper().getKeyValues(relatedCld, relatedObj, true));
 230  
             workList.remove(relatedObjKeys);
 231  
         }
 232  
 
 233  
         // delete all remaining keys in workList
 234  
         ClassDescriptor cld = pb.getDescriptorRepository().getDescriptorFor(obj.getClass());
 235  
         ValueContainer[] pkValues = pb.serviceBrokerHelper().getKeyValues(cld, obj);
 236  
 
 237  
         String[] pkColumns = cod.getFksToThisClass();
 238  
         String[] fkColumns = cod.getFksToItemClass();
 239  
         String table = cod.getIndirectionTable();
 240  
         String deleteStmt;
 241  
 
 242  
         ValueContainer[] fkValues;
 243  
         Iterator iter = workList.iterator();
 244  
         while(iter.hasNext())
 245  
         {
 246  
             fkValues = ((MtoNBroker.Key) iter.next()).m_containers;
 247  
             deleteStmt = pb.serviceSqlGenerator().getDeleteMNStatement(table, pkColumns, fkColumns);
 248  
             pb.serviceJdbcAccess().executeUpdateSQL(deleteStmt, cld, pkValues, fkValues);
 249  
         }
 250  
     }
 251  
 
 252  
     /**
 253  
      * @param m2n
 254  
      */
 255  
     public void storeMtoNImplementor(MtoNImplementor m2n)
 256  
     {
 257  
         if(log.isDebugEnabled()) log.debug("Storing M2N implementor [" + m2n + "]");
 258  
         insertOrDeleteMtoNImplementor(m2n, true);
 259  
     }
 260  
 
 261  
     /**
 262  
      * @param m2n
 263  
      */
 264  
     public void deleteMtoNImplementor(MtoNImplementor m2n)
 265  
     {
 266  
         if(log.isDebugEnabled()) log.debug("Deleting M2N implementor [" + m2n + "]");
 267  
         insertOrDeleteMtoNImplementor(m2n, false);
 268  
     }
 269  
 
 270  
 
 271  
     /**
 272  
      * @see org.apache.ojb.broker.PersistenceBroker#deleteMtoNImplementor
 273  
      */
 274  
     private void insertOrDeleteMtoNImplementor(MtoNImplementor m2nImpl, boolean insert)
 275  
             throws PersistenceBrokerException
 276  
     {
 277  
         //look for a collection descriptor on left  such as left.element-class-ref='right'
 278  
         DescriptorRepository dr = pb.getDescriptorRepository();
 279  
 
 280  
         Object leftObject = m2nImpl.getLeftObject();
 281  
         Class leftClass = m2nImpl.getLeftClass();
 282  
         Object rightObject = m2nImpl.getRightObject();
 283  
         Class rightClass = m2nImpl.getRightClass();
 284  
 
 285  
         //are written per class, maybe referencing abstract classes or interfaces
 286  
         //so let's look for collection descriptors on the left class and try to
 287  
         // handle extents on teh right class
 288  
         ClassDescriptor leftCld = dr.getDescriptorFor(leftClass);
 289  
         ClassDescriptor rightCld = dr.getDescriptorFor(rightClass);
 290  
         //Vector leftColds = leftCld.getCollectionDescriptors();
 291  
         CollectionDescriptor wanted = m2nImpl.getLeftDescriptor();
 292  
 
 293  
         if(leftObject == null || rightObject == null)
 294  
         {
 295  
             //TODO: to be implemented, must change MtoNImplementor
 296  
             //deleteMtoNImplementor(wanted,leftObject) || deleteMtoNImplementor(wanted,rightObject)
 297  
             log.error("Can't handle MtoNImplementor in correct way, found a 'null' object");
 298  
         }
 299  
         else
 300  
         {
 301  
             //delete only one row
 302  
             ValueContainer[] leftPkValues = pb.serviceBrokerHelper().getKeyValues(leftCld, leftObject);
 303  
             ValueContainer[] rightPkValues = pb.serviceBrokerHelper().getKeyValues(rightCld, rightObject);
 304  
             String[] pkLeftColumns = wanted.getFksToThisClass();
 305  
             String[] pkRightColumns = wanted.getFksToItemClass();
 306  
             String table = wanted.getIndirectionTable();
 307  
             if(table == null) throw new PersistenceBrokerException("Can't remove MtoN implementor without an indirection table");
 308  
 
 309  
             String stmt;
 310  
             String[] cols = mergeColumns(pkLeftColumns, pkRightColumns);
 311  
             ValueContainer[] values = mergeContainer(leftPkValues, rightPkValues);
 312  
             if(insert)
 313  
             {
 314  
                 stmt = pb.serviceSqlGenerator().getInsertMNStatement(table, pkLeftColumns, pkRightColumns);
 315  
                 GenericObject gObj = new GenericObject(table, cols, values);
 316  
                 if(!tempObjects.contains(gObj))
 317  
                 {
 318  
                     pb.serviceJdbcAccess().executeUpdateSQL(stmt, leftCld, leftPkValues, rightPkValues);
 319  
                     tempObjects.add(gObj);
 320  
                 }
 321  
             }
 322  
             else
 323  
             {
 324  
                 stmt = pb.serviceSqlGenerator().getDeleteMNStatement(table, pkLeftColumns, pkRightColumns);
 325  
                 pb.serviceJdbcAccess().executeUpdateSQL(stmt, leftCld, leftPkValues, rightPkValues);
 326  
             }
 327  
         }
 328  
     }
 329  
 
 330  
     private String[] mergeColumns(String[] first, String[] second)
 331  
     {
 332  
         String[] cols = new String[first.length + second.length];
 333  
         System.arraycopy(first, 0, cols, 0, first.length);
 334  
         System.arraycopy(second, 0, cols, first.length, second.length);
 335  
         return cols;
 336  
     }
 337  
 
 338  
     private ValueContainer[] mergeContainer(ValueContainer[] first, ValueContainer[] second)
 339  
     {
 340  
         ValueContainer[] values = new ValueContainer[first.length + second.length];
 341  
         System.arraycopy(first, 0, values, 0, first.length);
 342  
         System.arraycopy(second, 0, values, first.length, second.length);
 343  
         return values;
 344  
     }
 345  
 
 346  
 
 347  
 
 348  
 // ************************************************************************
 349  
 // inner class
 350  
 // ************************************************************************
 351  
 
 352  
     /**
 353  
      * This is a helper class to model a Key of an Object
 354  
      */
 355  
     private static final class Key
 356  
     {
 357  
         final ValueContainer[] m_containers;
 358  
 
 359  
         Key(final ValueContainer[] containers)
 360  
         {
 361  
             m_containers = new ValueContainer[containers.length];
 362  
 
 363  
             for(int i = 0; i < containers.length; i++)
 364  
             {
 365  
                 Object value = containers[i].getValue();
 366  
                 JdbcType type = containers[i].getJdbcType();
 367  
 
 368  
                 // BRJ:
 369  
                 // convert all Numbers to Long to simplify equals
 370  
                 // Long(100) is not equal to Integer(100)
 371  
                 //
 372  
                 // could lead to problems when Floats are used as key
 373  
                 // converting to String could be a better alternative
 374  
                 if(value instanceof Number)
 375  
                 {
 376  
                     value = new Long(((Number) value).longValue());
 377  
                 }
 378  
 
 379  
                 m_containers[i] = new ValueContainer(value, type);
 380  
             }
 381  
         }
 382  
 
 383  
         public boolean equals(Object other)
 384  
         {
 385  
             if(other == this)
 386  
             {
 387  
                 return true;
 388  
             }
 389  
             if(!(other instanceof Key))
 390  
             {
 391  
                 return false;
 392  
             }
 393  
 
 394  
             Key otherKey = (Key) other;
 395  
             EqualsBuilder eb = new EqualsBuilder();
 396  
 
 397  
             eb.append(m_containers, otherKey.m_containers);
 398  
             return eb.isEquals();
 399  
         }
 400  
 
 401  
         public int hashCode()
 402  
         {
 403  
             HashCodeBuilder hb = new HashCodeBuilder();
 404  
             hb.append(m_containers);
 405  
 
 406  
             return hb.toHashCode();
 407  
         }
 408  
     }
 409  
 
 410  
 
 411  
 
 412  
     // ************************************************************************
 413  
     // inner class
 414  
     // ************************************************************************
 415  
     private static final class GenericObject
 416  
     {
 417  
          private String tablename;
 418  
         private String[] columnNames;
 419  
         private ValueContainer[] values;
 420  
 
 421  
         public GenericObject(String tablename, String[] columnNames, ValueContainer[] values)
 422  
         {
 423  
             this.tablename = tablename;
 424  
             this.columnNames = columnNames;
 425  
             this.values = values;
 426  
             if(values != null && columnNames.length != values.length)
 427  
             {
 428  
                 throw new OJBRuntimeException("Column name array and value array have NOT same length");
 429  
             }
 430  
         }
 431  
 
 432  
         public boolean equals(Object obj)
 433  
         {
 434  
             if(this == obj)
 435  
             {
 436  
                 return true;
 437  
             }
 438  
             boolean result = false;
 439  
             if(obj instanceof GenericObject)
 440  
             {
 441  
                 GenericObject other = (GenericObject) obj;
 442  
                 result = (tablename.equalsIgnoreCase(other.tablename)
 443  
                         && (columnNames != null)
 444  
                         && (other.columnNames != null)
 445  
                         && (columnNames.length == other.columnNames.length));
 446  
 
 447  
                 if(result)
 448  
                 {
 449  
                     for (int i = 0; i < columnNames.length; i++)
 450  
                     {
 451  
                         int otherIndex = other.indexForColumn(columnNames[i]);
 452  
                         if(otherIndex < 0)
 453  
                         {
 454  
                             result = false;
 455  
                             break;
 456  
                         }
 457  
                         result = values[i].equals(other.values[otherIndex]);
 458  
                         if(!result) break;
 459  
                     }
 460  
                 }
 461  
             }
 462  
             return result;
 463  
         }
 464  
 
 465  
         int indexForColumn(String name)
 466  
         {
 467  
             int result = -1;
 468  
             for (int i = 0; i < columnNames.length; i++)
 469  
             {
 470  
                 if(columnNames[i].equals(name))
 471  
                 {
 472  
                     result = i;
 473  
                     break;
 474  
                 }
 475  
             }
 476  
             return result;
 477  
         }
 478  
 
 479  
         public int hashCode()
 480  
         {
 481  
             return super.hashCode();
 482  
         }
 483  
 
 484  
         public ValueContainer getValueFor(String columnName)
 485  
         {
 486  
             try
 487  
             {
 488  
                 return values[indexForColumn(columnName)];
 489  
             }
 490  
             catch(Exception e)
 491  
             {
 492  
                 throw new OJBRuntimeException("Can't find value for column " + columnName
 493  
                         + (indexForColumn(columnName) < 0 ? ". Column name was not found" : ""), e);
 494  
             }
 495  
         }
 496  
 
 497  
         public String getTablename()
 498  
         {
 499  
             return tablename;
 500  
         }
 501  
 
 502  
         public String[] getColumnNames()
 503  
         {
 504  
             return columnNames;
 505  
         }
 506  
 
 507  
         public ValueContainer[] getValues()
 508  
         {
 509  
             return values;
 510  
         }
 511  
 
 512  
         public void setValues(ValueContainer[] values)
 513  
         {
 514  
             this.values = values;
 515  
         }
 516  
 
 517  
         public String toString()
 518  
         {
 519  
             return new ToStringBuilder(this)
 520  
                     .append("tableName", tablename)
 521  
                     .append("columnNames", columnNames)
 522  
                     .append("values", values)
 523  
                     .toString();
 524  
         }
 525  
     }
 526  
 }