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.36.2.14 2005/12/21 22:22:07 tomdz Exp $
070: */
071: public class Identity implements Serializable {
072: /** Unique id for serialization purposes. */
073: private static final long serialVersionUID = 3182285550574178710L;
074:
075: private static final int IS_TRANSIENT = 3;
076: private static final int IS_PERMANENT = 17;
077: /**
078: * Used for hashCode calculation.
079: */
080: private static final int iConstant = 37;
081:
082: /**
083: * The top-level Class of the identified object, ie. an interface.
084: */
085: private Class m_objectsTopLevelClass;
086:
087: /**
088: * The real Class of the identified object, ie. the implementing class.
089: */
090: private Class m_objectsRealClass = null;
091:
092: /**
093: * The ordered list of primary key values maintaining the objects identity in the underlying RDBMS.
094: */
095: private Object[] m_pkValues;
096:
097: private final int isTransient;
098:
099: /*
100: the hashcode of different objects has to be unique across different
101: JVM and have to be the same for the same object in different JVM.
102:
103: In distributed enviroments the Identity object have to recalculate the
104: hashCode and toString values, because the hash code of the Class object
105: differs in different JVM
106: */
107: private transient String m_stringRepresentation = null;
108: private transient Integer m_hashCode;
109:
110: /**
111: * For internal use only!
112: */
113: protected Identity() {
114: isTransient = IS_TRANSIENT;
115: }
116:
117: /**
118: * For internal use only!. Creates an em from a class and the objects primary key values.
119: * used for the definition of proxies.
120: * <br/>
121: * OJB user have to use {@link IdentityFactory} to create object identity.
122: *
123: *
124: * @param realClass the concrete class of the object, or null if not known.
125: * @param topLevel the highest persistence-capable class or
126: * interface (in the inheritance hierarchy) that the identified object is an instance of
127: * @param pkValues (unique across the extents !)
128: * @param isTransient If <em>true</em>
129: */
130: public Identity(final Class realClass, final Class topLevel,
131: final Object[] pkValues, final boolean isTransient) {
132: m_objectsTopLevelClass = topLevel;
133: m_objectsRealClass = realClass;
134: m_pkValues = pkValues;
135: this .isTransient = isTransient ? IS_TRANSIENT : IS_PERMANENT;
136: checkForPrimaryKeys(null);
137: }
138:
139: /**
140: * For internal use only! Creates an Identity from a class and the objects primary key values.
141: * used for the definition of proxies.
142: * <br/>
143: * OJB user have to use {@link IdentityFactory} to create object identity.
144: *
145: * @param realClass the concrete class of the object, or null if not known.
146: * @param topLevel the highest persistence-capable class or
147: * interface (in the inheritance hierarchy) that the identified object is an instance of
148: * @param pkValues (unique across the extents !)
149: */
150: public Identity(final Class realClass, final Class topLevel,
151: final Object[] pkValues) {
152: m_objectsTopLevelClass = topLevel;
153: m_objectsRealClass = realClass;
154: m_pkValues = pkValues;
155: this .isTransient = IS_PERMANENT;
156: checkForPrimaryKeys(null);
157: }
158:
159: /**
160: * Constructor for internal use. Use {@link IdentityFactory} to create an object identity.
161: *
162: * @param objectToIdentitify The object for which to create the identity
163: * @param targetBroker The persistence broker
164: */
165: public Identity(final Object objectToIdentitify,
166: final PersistenceBroker targetBroker) {
167: this .isTransient = IS_PERMANENT;
168: init(objectToIdentitify, targetBroker, null);
169: }
170:
171: /**
172: * Constructor for internal use. Use {@link IdentityFactory} to create an object identity.
173: *
174: * @param objectToIdentitify The object for which to create the identity
175: * @param targetBroker The persistence broker
176: * @param cld The class descriptor
177: */
178: public Identity(final Object objectToIdentitify,
179: final PersistenceBroker targetBroker,
180: final ClassDescriptor cld) {
181: this .isTransient = IS_PERMANENT;
182: init(objectToIdentitify, targetBroker, cld);
183: }
184:
185: private void init(final Object objectToIdentify,
186: final PersistenceBroker targetBroker, ClassDescriptor cld) {
187: if (objectToIdentify == null)
188: throw new OJBRuntimeException(
189: "Can't create Identity for 'null'-object");
190: try {
191: final IndirectionHandler handler = ProxyHelper
192: .getIndirectionHandler(objectToIdentify);
193:
194: synchronized (objectToIdentify) {
195: if (handler != null) {
196: final Identity sourceOID = handler.getIdentity();
197: m_objectsTopLevelClass = sourceOID.m_objectsTopLevelClass;
198: m_objectsRealClass = sourceOID.m_objectsRealClass;
199: m_pkValues = sourceOID.m_pkValues;
200: } else {
201: if (cld == null) {
202: cld = targetBroker
203: .getClassDescriptor(objectToIdentify
204: .getClass());
205: }
206:
207: // identities must be unique accross extents !
208: m_objectsTopLevelClass = targetBroker
209: .getTopLevelClass(objectToIdentify
210: .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
216: .serviceBrokerHelper();
217: final ValueContainer[] pkValues = helper
218: .getValuesForObject(cld.getPkFields(),
219: objectToIdentify, false, true);
220: if (pkValues == null || pkValues.length == 0) {
221: throw createException(
222: "Can't extract PK value fields",
223: objectToIdentify, null);
224: }
225: m_pkValues = helper.extractValueArray(pkValues);
226: }
227: }
228:
229: checkForPrimaryKeys(objectToIdentify);
230: } catch (ClassNotPersistenceCapableException e) {
231: throw e;
232: } catch (Exception e) {
233: throw createException(
234: "Can not init Identity for given object.",
235: objectToIdentify, e);
236: }
237: }
238:
239: /**
240: * Factory method that returns an Identity object created from a serializated representation.
241: *
242: * @param anArray The serialized representation
243: * @return The identity
244: * @see {@link #serialize}.
245: * @deprecated
246: */
247: public static Identity fromByteArray(final byte[] anArray)
248: throws PersistenceBrokerException {
249: // reverse of the serialize() algorithm:
250: // read from byte[] with a ByteArrayInputStream, decompress with
251: // a GZIPInputStream and then deserialize by reading from the ObjectInputStream
252: try {
253: final ByteArrayInputStream bais = new ByteArrayInputStream(
254: anArray);
255: final GZIPInputStream gis = new GZIPInputStream(bais);
256: final ObjectInputStream ois = new ObjectInputStream(gis);
257: final Identity result = (Identity) ois.readObject();
258: ois.close();
259: gis.close();
260: bais.close();
261: return result;
262: } catch (Exception ex) {
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: return isTransient == IS_TRANSIENT;
274: }
275:
276: /**
277: * Returns the top-level class of the real subject (base class,
278: * base interface denoted in the repository or
279: * objects real class if no top-level was found).
280: *
281: * @return The top level class
282: */
283: public Class getObjectsTopLevelClass() {
284: return m_objectsTopLevelClass;
285: }
286:
287: /**
288: * Return the "real" class of the real subject.
289: *
290: * @return The real class
291: */
292: public Class getObjectsRealClass() {
293: return m_objectsRealClass;
294: }
295:
296: /**
297: * Set the real class of the subject.
298: *
299: * @param objectsRealClass The real class
300: */
301: public void setObjectsRealClass(final Class objectsRealClass) {
302: this .m_objectsRealClass = objectsRealClass;
303: }
304:
305: /**
306: * Return the serialized form of this Identity.
307: *
308: * @return The serialized representation
309: * @see #fromByteArray
310: * @deprecated
311: */
312: public byte[] serialize() throws PersistenceBrokerException {
313: // Identity is serialized and written to an ObjectOutputStream
314: // This ObjectOutputstream is compressed by a GZIPOutputStream
315: // and finally written to a ByteArrayOutputStream.
316: // the resulting byte[] is returned
317: try {
318: final ByteArrayOutputStream bao = new ByteArrayOutputStream();
319: final GZIPOutputStream gos = new GZIPOutputStream(bao);
320: final ObjectOutputStream oos = new ObjectOutputStream(gos);
321: oos.writeObject(this );
322: oos.close();
323: gos.close();
324: bao.close();
325: return bao.toByteArray();
326: } catch (Exception ignored) {
327: throw new PersistenceBrokerException(ignored);
328: }
329: }
330:
331: /**
332: * return a String representation.
333: * @return java.lang.String
334: */
335: public String toString() {
336: if (m_stringRepresentation == null) {
337: final StringBuffer buf = new StringBuffer();
338: buf.append(m_objectsTopLevelClass.getName());
339: for (int i = 0; i < m_pkValues.length; i++) {
340: buf.append((i == 0) ? "{" : ",");
341: buf.append(m_pkValues[i]);
342: }
343: buf.append("}");
344: if (isTransient == IS_TRANSIENT)
345: buf.append("-transient");
346: m_stringRepresentation = buf.toString();
347: }
348: return m_stringRepresentation;
349: }
350:
351: /**
352: * OJB can handle only classes that declare at least one primary key attribute,
353: * this method checks this condition.
354: *
355: * @param realObject The real object to check
356: * @throws ClassNotPersistenceCapableException thrown if no primary key is specified for the objects class
357: */
358: protected void checkForPrimaryKeys(final Object realObject)
359: throws ClassNotPersistenceCapableException {
360: // if no PKs are specified OJB can't handle this class !
361: if (m_pkValues == null || m_pkValues.length == 0) {
362: throw createException(
363: "OJB needs at least one primary key attribute for class: ",
364: realObject, null);
365: }
366: // arminw: should never happen
367: // if(m_pkValues[0] instanceof ValueContainer)
368: // throw new OJBRuntimeException("Can't handle pk values of type "+ValueContainer.class.getName());
369: }
370:
371: /**
372: * Returns the primary key values of the real subject.
373: *
374: * @return The pk values
375: */
376: public Object[] getPrimaryKeyValues() {
377: return m_pkValues;
378: }
379:
380: /**
381: * {@inheritDoc}
382: */
383: public boolean equals(final Object obj) {
384: if (this == obj)
385: return true;
386:
387: boolean result = false;
388: if (obj instanceof Identity) {
389: final Identity id = (Identity) obj;
390: result = m_objectsTopLevelClass
391: .equals(id.m_objectsTopLevelClass)
392: && isTransient == id.isTransient;
393: if (result) {
394: final Object[] otherPkValues = id.m_pkValues;
395: result = m_pkValues.length == otherPkValues.length;
396: if (result) {
397: for (int i = 0; result && i < m_pkValues.length; i++) {
398: result = (m_pkValues[i] == null) ? (otherPkValues[i] == null)
399: : m_pkValues[i]
400: .equals(otherPkValues[i]);
401:
402: // special treatment for byte[]
403: if (!result && m_pkValues[i] instanceof byte[]
404: && otherPkValues[i] instanceof byte[]) {
405: result = Arrays.equals(
406: (byte[]) m_pkValues[i],
407: (byte[]) otherPkValues[i]);
408: }
409: }
410: }
411: }
412: }
413: return result;
414: }
415:
416: /**
417: * {@inheritDoc}
418: */
419: public int hashCode() {
420: /*
421: arminw:
422: identity is quasi immutable (toplevel class and PK fields
423: never change), thus we can note hashCode
424: */
425: if (m_hashCode == null) {
426: int iTotal = isTransient;
427: Object obj;
428: for (int i = 0; i < m_pkValues.length; i++) {
429: obj = m_pkValues[i];
430: if (obj instanceof byte[]) {
431: iTotal = iTotal * iConstant + ((byte[]) obj).length;
432: } else {
433: iTotal = iTotal * iConstant
434: + (obj != null ? obj.hashCode() : 0);
435: }
436: }
437: iTotal = iTotal * iConstant
438: + m_objectsTopLevelClass.hashCode();
439: m_hashCode = new Integer(iTotal);
440: }
441: return m_hashCode.intValue();
442: }
443:
444: private ClassNotPersistenceCapableException createException(
445: String msg, final Object objectToIdentify, final Exception e) {
446: final String eol = SystemUtils.LINE_SEPARATOR;
447: if (msg == null) {
448: msg = "Unexpected error:";
449: }
450: if (e != null) {
451: return new ClassNotPersistenceCapableException(
452: msg
453: + eol
454: + "objectTopLevelClass="
455: + (m_objectsTopLevelClass != null ? m_objectsTopLevelClass
456: .getName()
457: : null)
458: + eol
459: + "objectRealClass="
460: + (m_objectsRealClass != null ? m_objectsRealClass
461: .getName()
462: : null)
463: + eol
464: + "pkValues="
465: + (m_pkValues != null ? ArrayUtils
466: .toString(m_pkValues) : null)
467: + (objectToIdentify != null ? (eol
468: + "object to identify: " + objectToIdentify)
469: : ""), e);
470: } else {
471: return new ClassNotPersistenceCapableException(
472: msg
473: + eol
474: + "objectTopLevelClass="
475: + (m_objectsTopLevelClass != null ? m_objectsTopLevelClass
476: .getName()
477: : null)
478: + eol
479: + "objectRealClass="
480: + (m_objectsRealClass != null ? m_objectsRealClass
481: .getName()
482: : null)
483: + eol
484: + "pkValues="
485: + (m_pkValues != null ? ArrayUtils
486: .toString(m_pkValues) : null) + eol
487: + "object to identify: " + objectToIdentify);
488: }
489: }
490: }
|