001    package org.apache.ojb.broker;
002    
003    /* Copyright 2002-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    import org.apache.ojb.broker.metadata.ClassDescriptor;
019    import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
020    import org.apache.ojb.broker.core.ValueContainer;
021    import org.apache.ojb.broker.core.proxy.IndirectionHandler;
022    import org.apache.ojb.broker.core.proxy.ProxyHelper;
023    import org.apache.ojb.broker.util.BrokerHelper;
024    import org.apache.commons.lang.SystemUtils;
025    import org.apache.commons.lang.ArrayUtils;
026    
027    import java.io.ByteArrayInputStream;
028    import java.io.ByteArrayOutputStream;
029    import java.io.ObjectInputStream;
030    import java.io.ObjectOutputStream;
031    import java.io.Serializable;
032    import java.util.Arrays;
033    import java.util.zip.GZIPInputStream;
034    import java.util.zip.GZIPOutputStream;
035    
036    /**
037     * Represents the identity of an object.
038     * <br/>
039     * It's composed of:
040     * <ul>
041     * <li>
042     * class of the real object
043     * </li>
044     * <li>
045     * top-level class of the real object (could be an abstract class or interface or the
046     * class of the object itself), used to make an object unique across extent classes
047     * </li>
048     * <li>
049     * an array of all primary key value objects
050     * </li>
051     * <li>
052     * a flag which indicates whether this is a <em>transient Identity</em>
053     * (identity of a non-persistent, "new" object) or a <em>persistent Identity</em> (identity object
054     * of a persistent, "already written to datastore" object).
055     * </li>
056     * </ul>
057     * <p>
058     * To create <code>Identity</code> objects it's strongly recommended to use the {@link IdentityFactory}, because
059     * in future releases of OJB the <code>Identity</code> constructors will be no longer reachable or forbidden to use.
060     * </p>
061     * <p>
062     * NOTE: An <em>Identity</em> object must be unique
063     * accross extents. Means all objects with the same top-level class need unique
064     * PK values.
065     * </p>
066     * @see org.apache.ojb.broker.IdentityFactory
067    
068     * @author Thomas Mahler
069     * @version $Id: Identity.java,v 1.1 2007-08-24 22:17:35 ewestfal Exp $
070     */
071    public class Identity implements Serializable
072    {
073        /** Unique id for serialization purposes. */ 
074        private static final long serialVersionUID = 3182285550574178710L;
075    
076        private static final int IS_TRANSIENT = 3;
077        private static final int IS_PERMANENT = 17;
078        /**
079         * Used for hashCode calculation.
080         */
081        private static final int iConstant = 37;
082    
083        /**
084         * The top-level Class of the identified object, ie. an interface.
085         */
086        private Class m_objectsTopLevelClass;
087    
088        /**
089         * The real Class of the identified object, ie. the implementing class.
090         */
091        private Class m_objectsRealClass = null;
092    
093        /**
094         * The ordered list of primary key values maintaining the objects identity in the underlying RDBMS.
095         */
096        private Object[] m_pkValues;
097    
098        private final int isTransient;
099    
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    }