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 }