Coverage Report - org.apache.ojb.broker.Identity
 
Classes in this File Line Coverage Branch Coverage Complexity
Identity
N/A
N/A
3.833
 
 1  
 package org.apache.ojb.broker;
 2  
 
 3  
 /* Copyright 2002-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 org.apache.ojb.broker.metadata.ClassDescriptor;
 19  
 import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
 20  
 import org.apache.ojb.broker.core.ValueContainer;
 21  
 import org.apache.ojb.broker.core.proxy.IndirectionHandler;
 22  
 import org.apache.ojb.broker.core.proxy.ProxyHelper;
 23  
 import org.apache.ojb.broker.util.BrokerHelper;
 24  
 import org.apache.commons.lang.SystemUtils;
 25  
 import org.apache.commons.lang.ArrayUtils;
 26  
 
 27  
 import java.io.ByteArrayInputStream;
 28  
 import java.io.ByteArrayOutputStream;
 29  
 import java.io.ObjectInputStream;
 30  
 import java.io.ObjectOutputStream;
 31  
 import java.io.Serializable;
 32  
 import java.util.Arrays;
 33  
 import java.util.zip.GZIPInputStream;
 34  
 import java.util.zip.GZIPOutputStream;
 35  
 
 36  
 /**
 37  
  * Represents the identity of an object.
 38  
  * <br/>
 39  
  * It's composed of:
 40  
  * <ul>
 41  
  * <li>
 42  
  * class of the real object
 43  
  * </li>
 44  
  * <li>
 45  
  * top-level class of the real object (could be an abstract class or interface or the
 46  
  * class of the object itself), used to make an object unique across extent classes
 47  
  * </li>
 48  
  * <li>
 49  
  * an array of all primary key value objects
 50  
  * </li>
 51  
  * <li>
 52  
  * a flag which indicates whether this is a <em>transient Identity</em>
 53  
  * (identity of a non-persistent, "new" object) or a <em>persistent Identity</em> (identity object
 54  
  * of a persistent, "already written to datastore" object).
 55  
  * </li>
 56  
  * </ul>
 57  
  * <p>
 58  
  * To create <code>Identity</code> objects it's strongly recommended to use the {@link IdentityFactory}, because
 59  
  * in future releases of OJB the <code>Identity</code> constructors will be no longer reachable or forbidden to use.
 60  
  * </p>
 61  
  * <p>
 62  
  * NOTE: An <em>Identity</em> object must be unique
 63  
  * accross extents. Means all objects with the same top-level class need unique
 64  
  * PK values.
 65  
  * </p>
 66  
  * @see org.apache.ojb.broker.IdentityFactory
 67  
 
 68  
  * @author Thomas Mahler
 69  
  * @version $Id: Identity.java,v 1.1 2007-08-24 22:17:35 ewestfal Exp $
 70  
  */
 71  
 public class Identity implements Serializable
 72  
 {
 73  
     /** Unique id for serialization purposes. */ 
 74  
     private static final long serialVersionUID = 3182285550574178710L;
 75  
 
 76  
     private static final int IS_TRANSIENT = 3;
 77  
     private static final int IS_PERMANENT = 17;
 78  
     /**
 79  
      * Used for hashCode calculation.
 80  
      */
 81  
     private static final int iConstant = 37;
 82  
 
 83  
     /**
 84  
      * The top-level Class of the identified object, ie. an interface.
 85  
      */
 86  
     private Class m_objectsTopLevelClass;
 87  
 
 88  
     /**
 89  
      * The real Class of the identified object, ie. the implementing class.
 90  
      */
 91  
     private Class m_objectsRealClass = null;
 92  
 
 93  
     /**
 94  
      * The ordered list of primary key values maintaining the objects identity in the underlying RDBMS.
 95  
      */
 96  
     private Object[] m_pkValues;
 97  
 
 98  
     private final int isTransient;
 99  
 
 100  
     /*
 101  
     the hashcode of different objects has to be unique across different
 102  
     JVM and have to be the same for the same object in different JVM.
 103  
 
 104  
     In distributed enviroments the Identity object have to recalculate the
 105  
     hashCode and toString values, because the hash code of the Class object
 106  
     differs in different JVM
 107  
     */
 108  
     private transient String m_stringRepresentation = null;
 109  
     private transient Integer m_hashCode;
 110  
 
 111  
     /**
 112  
      * For internal use only!
 113  
      */
 114  
     protected Identity()
 115  
     {
 116  
         isTransient = IS_TRANSIENT;
 117  
     }
 118  
 
 119  
     /**
 120  
      * For internal use only!. Creates an em from a class and the objects primary key values.
 121  
      * used for the definition of proxies.
 122  
      * <br/>
 123  
      * OJB user have to use {@link IdentityFactory} to create object identity.
 124  
      *
 125  
      *
 126  
      * @param realClass the concrete class of the object, or null if not known.
 127  
      * @param topLevel the highest persistence-capable class or
 128  
      * interface (in the inheritance hierarchy) that the identified object is an instance of
 129  
      * @param pkValues (unique across the extents !)
 130  
      * @param isTransient If <em>true</em>
 131  
      */
 132  
     public Identity(final Class realClass, final Class topLevel, final Object[] pkValues, final boolean isTransient)
 133  
     {
 134  
         m_objectsTopLevelClass = topLevel;
 135  
         m_objectsRealClass = realClass;
 136  
         m_pkValues = pkValues;
 137  
         this.isTransient = isTransient ? IS_TRANSIENT : IS_PERMANENT;
 138  
         checkForPrimaryKeys(null);
 139  
     }
 140  
 
 141  
     /**
 142  
      * For internal use only! Creates an Identity from a class and the objects primary key values.
 143  
      * used for the definition of proxies.
 144  
      * <br/>
 145  
      * OJB user have to use {@link IdentityFactory} to create object identity.
 146  
      *
 147  
      * @param realClass the concrete class of the object, or null if not known.
 148  
      * @param topLevel the highest persistence-capable class or
 149  
      * interface (in the inheritance hierarchy) that the identified object is an instance of
 150  
      * @param pkValues (unique across the extents !)
 151  
      */
 152  
     public Identity(final Class realClass, final Class topLevel, final Object[] pkValues)
 153  
     {
 154  
         m_objectsTopLevelClass = topLevel;
 155  
         m_objectsRealClass = realClass;
 156  
         m_pkValues = pkValues;
 157  
         this.isTransient = IS_PERMANENT;
 158  
         checkForPrimaryKeys(null);
 159  
     }
 160  
 
 161  
     /**
 162  
      * Constructor for internal use. Use {@link IdentityFactory} to create an object identity.
 163  
      * 
 164  
      * @param objectToIdentitify The object for which to create the identity
 165  
      * @param targetBroker       The persistence broker
 166  
      */
 167  
     public Identity(final Object objectToIdentitify, final PersistenceBroker targetBroker)
 168  
     {
 169  
         this.isTransient = IS_PERMANENT;
 170  
         init(objectToIdentitify, targetBroker, null);
 171  
     }
 172  
 
 173  
     /**
 174  
      * Constructor for internal use. Use {@link IdentityFactory} to create an object identity.
 175  
      * 
 176  
      * @param objectToIdentitify The object for which to create the identity
 177  
      * @param targetBroker       The persistence broker
 178  
      * @param cld                The class descriptor
 179  
      */
 180  
     public Identity(final Object objectToIdentitify, final PersistenceBroker targetBroker, final ClassDescriptor cld)
 181  
     {
 182  
         this.isTransient = IS_PERMANENT;
 183  
         init(objectToIdentitify, targetBroker, cld);
 184  
     }
 185  
 
 186  
     private void init(final Object objectToIdentify, final PersistenceBroker targetBroker, ClassDescriptor cld)
 187  
     {
 188  
         if(objectToIdentify == null) throw new OJBRuntimeException("Can't create Identity for 'null'-object");
 189  
         try
 190  
         {
 191  
             final IndirectionHandler handler = ProxyHelper.getIndirectionHandler(objectToIdentify);
 192  
 
 193  
             synchronized(objectToIdentify)
 194  
             {
 195  
                 if (handler != null)
 196  
                 {
 197  
                     final Identity sourceOID = handler.getIdentity();
 198  
                     m_objectsTopLevelClass = sourceOID.m_objectsTopLevelClass;
 199  
                     m_objectsRealClass = sourceOID.m_objectsRealClass;
 200  
                     m_pkValues = sourceOID.m_pkValues;
 201  
                 }
 202  
                 else
 203  
                 {
 204  
                     if (cld == null)
 205  
                     {
 206  
                         cld = targetBroker.getClassDescriptor(objectToIdentify.getClass());
 207  
                     }
 208  
 
 209  
                     // identities must be unique accross extents !
 210  
                     m_objectsTopLevelClass = targetBroker.getTopLevelClass(objectToIdentify.getClass());
 211  
                     m_objectsRealClass = objectToIdentify.getClass();
 212  
 
 213  
                     // BRJ: definitely do NOT convertToSql
 214  
                     // conversion is done when binding the sql-statement
 215  
                     final BrokerHelper helper = targetBroker.serviceBrokerHelper();
 216  
                     final ValueContainer[] pkValues = helper.getValuesForObject(cld.getPkFields(), objectToIdentify, false, true);
 217  
                     if (pkValues == null || pkValues.length == 0)
 218  
                     {
 219  
                         throw createException("Can't extract PK value fields", objectToIdentify, null);
 220  
                     }
 221  
                     m_pkValues = helper.extractValueArray(pkValues);
 222  
                 }
 223  
             }
 224  
 
 225  
             checkForPrimaryKeys(objectToIdentify);
 226  
         }
 227  
         catch (ClassNotPersistenceCapableException e)
 228  
         {
 229  
             throw e;
 230  
         }
 231  
         catch (Exception e)
 232  
         {
 233  
             throw createException("Can not init Identity for given object.", objectToIdentify, e);
 234  
         }
 235  
     }
 236  
 
 237  
     /**
 238  
      * Factory method that returns an Identity object created from a serializated representation.
 239  
      * 
 240  
      * @param anArray The serialized representation
 241  
      * @return The identity
 242  
      * @see {@link #serialize}.
 243  
      * @deprecated
 244  
      */
 245  
     public static Identity fromByteArray(final byte[] anArray) throws PersistenceBrokerException
 246  
     {
 247  
         // reverse of the serialize() algorithm:
 248  
         // read from byte[] with a ByteArrayInputStream, decompress with
 249  
         // a GZIPInputStream and then deserialize by reading from the ObjectInputStream
 250  
         try
 251  
         {
 252  
             final ByteArrayInputStream bais = new ByteArrayInputStream(anArray);
 253  
             final GZIPInputStream gis = new GZIPInputStream(bais);
 254  
             final ObjectInputStream ois = new ObjectInputStream(gis);
 255  
             final Identity result = (Identity) ois.readObject();
 256  
             ois.close();
 257  
             gis.close();
 258  
             bais.close();
 259  
             return result;
 260  
         }
 261  
         catch (Exception ex)
 262  
         {
 263  
             throw new PersistenceBrokerException(ex);
 264  
         }
 265  
     }
 266  
 
 267  
     /**
 268  
      * Determines whether the identity is transient.
 269  
      * 
 270  
      * @return <code>true</code> if the identity is transient
 271  
      */
 272  
     public boolean isTransient()
 273  
     {
 274  
         return isTransient == IS_TRANSIENT;
 275  
     }
 276  
 
 277  
     /**
 278  
      * Returns the top-level class of the real subject (base class,
 279  
      * base interface denoted in the repository or
 280  
      * objects real class if no top-level was found).
 281  
      *
 282  
      * @return The top level class
 283  
      */
 284  
     public Class getObjectsTopLevelClass()
 285  
     {
 286  
         return m_objectsTopLevelClass;
 287  
     }
 288  
 
 289  
     /**
 290  
      * Return the "real" class of the real subject.
 291  
      * 
 292  
      * @return The real class
 293  
      */
 294  
     public Class getObjectsRealClass()
 295  
     {
 296  
         return m_objectsRealClass;
 297  
     }
 298  
 
 299  
     /**
 300  
      * Set the real class of the subject.
 301  
      * 
 302  
      * @param objectsRealClass The real class
 303  
      */
 304  
     public void setObjectsRealClass(final Class objectsRealClass)
 305  
     {
 306  
         this.m_objectsRealClass = objectsRealClass;
 307  
     }
 308  
 
 309  
     /**
 310  
      * Return the serialized form of this Identity.
 311  
      * 
 312  
      * @return The serialized representation
 313  
      * @see #fromByteArray
 314  
      * @deprecated
 315  
      */
 316  
     public byte[] serialize() throws PersistenceBrokerException
 317  
     {
 318  
         // Identity is serialized and written to an ObjectOutputStream
 319  
         // This ObjectOutputstream is compressed by a GZIPOutputStream
 320  
         // and finally written to a ByteArrayOutputStream.
 321  
         // the resulting byte[] is returned
 322  
         try
 323  
         {
 324  
             final ByteArrayOutputStream bao = new ByteArrayOutputStream();
 325  
             final GZIPOutputStream gos = new GZIPOutputStream(bao);
 326  
             final ObjectOutputStream oos = new ObjectOutputStream(gos);
 327  
             oos.writeObject(this);
 328  
             oos.close();
 329  
             gos.close();
 330  
             bao.close();
 331  
             return bao.toByteArray();
 332  
         }
 333  
         catch (Exception ignored)
 334  
         {
 335  
             throw new PersistenceBrokerException(ignored);
 336  
         }
 337  
     }
 338  
 
 339  
     /**
 340  
      * return a String representation.
 341  
      * @return java.lang.String
 342  
      */
 343  
     public String toString()
 344  
     {
 345  
         if (m_stringRepresentation == null)
 346  
         {
 347  
             final StringBuffer buf = new StringBuffer();
 348  
             buf.append(m_objectsTopLevelClass.getName());
 349  
             for (int i = 0; i < m_pkValues.length; i++)
 350  
             {
 351  
                 buf.append((i == 0) ? "{" : ",");
 352  
                 buf.append(m_pkValues[i]);
 353  
             }
 354  
             buf.append("}");
 355  
             if(isTransient == IS_TRANSIENT) buf.append("-transient");
 356  
             m_stringRepresentation = buf.toString();
 357  
         }
 358  
         return m_stringRepresentation;
 359  
     }
 360  
 
 361  
 
 362  
     /**
 363  
      * OJB can handle only classes that declare at least one primary key attribute,
 364  
      * this method checks this condition.
 365  
      * 
 366  
      * @param realObject The real object to check
 367  
      * @throws ClassNotPersistenceCapableException thrown if no primary key is specified for the objects class
 368  
      */
 369  
     protected void checkForPrimaryKeys(final Object realObject) throws ClassNotPersistenceCapableException
 370  
     {
 371  
         // if no PKs are specified OJB can't handle this class !
 372  
         if (m_pkValues == null || m_pkValues.length == 0)
 373  
         {
 374  
             throw createException("OJB needs at least one primary key attribute for class: ", realObject, null);
 375  
         }
 376  
 // arminw: should never happen
 377  
 //        if(m_pkValues[0] instanceof ValueContainer)
 378  
 //            throw new OJBRuntimeException("Can't handle pk values of type "+ValueContainer.class.getName());
 379  
     }
 380  
 
 381  
     /**
 382  
      * Returns the primary key values of the real subject.
 383  
      * 
 384  
      * @return The pk values
 385  
      */
 386  
     public Object[] getPrimaryKeyValues()
 387  
     {
 388  
         return m_pkValues;
 389  
     }
 390  
 
 391  
     /**
 392  
      * {@inheritDoc}
 393  
      */
 394  
     public boolean equals(final Object obj)
 395  
     {
 396  
         if(this == obj) return true;
 397  
 
 398  
         boolean result = false;
 399  
         if (obj instanceof Identity)
 400  
         {
 401  
             final Identity id = (Identity) obj;
 402  
             result = m_objectsTopLevelClass.equals(id.m_objectsTopLevelClass) && isTransient == id.isTransient;
 403  
             if(result)
 404  
             {
 405  
                 final Object[] otherPkValues = id.m_pkValues;
 406  
                 result = m_pkValues.length == otherPkValues.length;
 407  
                 if(result)
 408  
                 {
 409  
                     for (int i = 0; result && i < m_pkValues.length; i++)
 410  
                     {
 411  
                         result = (m_pkValues[i] == null) ? (otherPkValues[i] == null)
 412  
                                 : m_pkValues[i].equals(otherPkValues[i]);
 413  
 
 414  
                         // special treatment for byte[]
 415  
                         if (!result && m_pkValues[i] instanceof byte[] && otherPkValues[i] instanceof byte[])
 416  
                         {
 417  
                             result = Arrays.equals((byte[]) m_pkValues[i], (byte[]) otherPkValues[i]);
 418  
                         }
 419  
                     }
 420  
                 }
 421  
             }
 422  
         }
 423  
         return result;
 424  
     }
 425  
 
 426  
     /**
 427  
      * {@inheritDoc}
 428  
      */
 429  
     public int hashCode()
 430  
     {
 431  
         /*
 432  
         arminw:
 433  
         identity is quasi immutable (toplevel class and PK fields
 434  
         never change), thus we can note hashCode
 435  
         */
 436  
         if(m_hashCode == null)
 437  
         {
 438  
             int iTotal = isTransient;
 439  
             Object obj;
 440  
             for (int i = 0; i < m_pkValues.length; i++)
 441  
             {
 442  
                 obj = m_pkValues[i];
 443  
                 if(obj instanceof byte[])
 444  
                 {
 445  
                     iTotal = iTotal * iConstant + ((byte[]) obj).length;
 446  
             }
 447  
                 else
 448  
                 {
 449  
                     iTotal = iTotal * iConstant + (obj != null ? obj.hashCode() : 0);
 450  
         }
 451  
             }
 452  
             iTotal = iTotal * iConstant + m_objectsTopLevelClass.hashCode();
 453  
             m_hashCode = new Integer(iTotal);
 454  
         }
 455  
         return m_hashCode.intValue();
 456  
     }
 457  
 
 458  
     private ClassNotPersistenceCapableException createException(String msg, final Object objectToIdentify, final Exception e)
 459  
     {
 460  
         final String eol = SystemUtils.LINE_SEPARATOR;
 461  
         if(msg == null)
 462  
         {
 463  
             msg = "Unexpected error:";
 464  
         }
 465  
         if(e != null)
 466  
         {
 467  
             return new ClassNotPersistenceCapableException(msg + eol +
 468  
                         "objectTopLevelClass=" + (m_objectsTopLevelClass != null ? m_objectsTopLevelClass.getName() : null) + eol +
 469  
                         "objectRealClass=" + (m_objectsRealClass != null ? m_objectsRealClass.getName() : null) + eol +
 470  
                         "pkValues=" + (m_pkValues != null ? ArrayUtils.toString(m_pkValues) : null) +
 471  
                         (objectToIdentify != null ? (eol + "object to identify: " + objectToIdentify) : ""), e);
 472  
         }
 473  
         else
 474  
         {
 475  
             return new ClassNotPersistenceCapableException(msg + eol +
 476  
                         "objectTopLevelClass=" + (m_objectsTopLevelClass != null ? m_objectsTopLevelClass.getName() : null) + eol +
 477  
                         "objectRealClass=" + (m_objectsRealClass != null ? m_objectsRealClass.getName() : null) + eol +
 478  
                         "pkValues=" + (m_pkValues != null ? ArrayUtils.toString(m_pkValues) : null) +
 479  
                         eol + "object to identify: " + objectToIdentify);
 480  
         }
 481  
     }
 482  
 }