package org.apache.ojb.broker;
/* Copyright 2002-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.apache.ojb.broker.metadata.ClassDescriptor;
import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
import org.apache.ojb.broker.core.ValueContainer;
import org.apache.ojb.broker.core.proxy.IndirectionHandler;
import org.apache.ojb.broker.core.proxy.ProxyHelper;
import org.apache.ojb.broker.util.BrokerHelper;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.lang.ArrayUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* Represents the identity of an object.
* <br/>
* It's composed of:
* <ul>
* <li>
* class of the real object
* </li>
* <li>
* top-level class of the real object (could be an abstract class or interface or the
* class of the object itself), used to make an object unique across extent classes
* </li>
* <li>
* an array of all primary key value objects
* </li>
* </ul>
* <p>
* If in the metadata of an persistent capable object class the attribute <em>autoincrement</em>
* is set true, new primary key values will be automatic assigned to the given object passed as
* constructor argument.
* </p>
* <p>
* NOTE: An <em>Identity</em> object must be unique
* accross extents. Means all objects with the same top-level class need unique
* PK values.
* </p>
* @see org.apache.ojb.broker.IdentityFactory
* @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
* @version $Id: Identity.java,v 1.36.2.8 2005/03/23 12:39:57 arminw Exp $
*/
public class Identity implements Serializable
{
static final long serialVersionUID = 3182285550574178710L;
/**
* the top-level Class of the identified object<br>
* ie: an Interface
*/
private Class m_objectsTopLevelClass;
/**
* the real Class of the identified object<br>
* ie: the implementing Class
*/
private Class m_objectsRealClass = null;
/**
* The ordered list of primary key values maintaining the objects identity in the underlying RDBMS
*/
private Object[] m_pkValues;
/*
In distributed enviroments the Identity object have to recalculate the
hashCode and toString values, because the hash code of the Class object
differs in different JVM
*/
private transient String m_stringRepresentation = null;
private transient Integer m_hashCode;
/**
* creates an Identity from a class and the objects primary key values.
* used for the definition of proxies.
*
* @param realClass the concrete class of the object, or null if not known.
* @param topLevel the highest persistence-capable class or
* interface (in the inheritance hierarchy) that the identified object is an instance of
* @param pkValues (unique across the extents !)
*/
public Identity(final Class realClass, final Class topLevel, final Object[] pkValues)
{
m_objectsTopLevelClass = topLevel;
m_objectsRealClass = realClass;
m_pkValues = pkValues;
checkForPrimaryKeys(null);
}
public Identity(final Object objectToIdentitify, final PersistenceBroker targetBroker)
{
init(objectToIdentitify, targetBroker, null);
}
public Identity(final Object objectToIdentitify, final PersistenceBroker targetBroker, final ClassDescriptor cld)
{
init(objectToIdentitify, targetBroker, cld);
}
private void init(final Object objectToIdentify, final PersistenceBroker targetBroker, ClassDescriptor cld)
{
if(objectToIdentify == null) throw new OJBRuntimeException("Can't create Identity for 'null'-object");
try
{
final IndirectionHandler handler = ProxyHelper.getIndirectionHandler(objectToIdentify);
synchronized(objectToIdentify)
{
if (handler != null)
{
final Identity sourceOID = handler.getIdentity();
m_objectsTopLevelClass = sourceOID.m_objectsTopLevelClass;
m_objectsRealClass = sourceOID.m_objectsRealClass;
m_pkValues = sourceOID.m_pkValues;
}
else
{
if (cld == null)
{
cld = targetBroker.getClassDescriptor(objectToIdentify.getClass());
}
// identities must be unique accross extents !
m_objectsTopLevelClass = targetBroker.getTopLevelClass(objectToIdentify.getClass());
m_objectsRealClass = objectToIdentify.getClass();
// BRJ: definitely do NOT convertToSql
// conversion is done when binding the sql-statement
final BrokerHelper helper = targetBroker.serviceBrokerHelper();
final ValueContainer[] pkValues = helper.getKeyValues(cld, objectToIdentify, false);
if (pkValues == null || pkValues.length == 0)
{
throw createException("Can't extract PK value fields", objectToIdentify, null);
}
m_pkValues = helper.extractValueArray(pkValues);
}
}
checkForPrimaryKeys(objectToIdentify);
}
catch (ClassNotPersistenceCapableException e)
{
throw e;
}
catch (Exception e)
{
throw createException("Can not init Identity for given object.", objectToIdentify, e);
}
}
/**
* Factory method that returns an Identity object from the given
* byte array - see {@link #serialize}.
*/
public static Identity fromByteArray(final byte[] anArray) throws PersistenceBrokerException
{
// reverse of the serialize() algorithm:
// read from byte[] with a ByteArrayInputStream, decompress with
// a GZIPInputStream and then deserialize by reading from the ObjectInputStream
try
{
final ByteArrayInputStream bais = new ByteArrayInputStream(anArray);
final GZIPInputStream gis = new GZIPInputStream(bais);
final ObjectInputStream ois = new ObjectInputStream(gis);
final Identity result = (Identity) ois.readObject();
ois.close();
gis.close();
bais.close();
return result;
}
catch (Exception ex)
{
throw new PersistenceBrokerException(ex);
}
}
/**
* Return the top-level class of the real
* subject (means class name of a base class,
* base interface denoted in the repository or
* objects real class name if none top-level was found)
*
*/
public Class getObjectsTopLevelClass()
{
return m_objectsTopLevelClass;
}
/**
* Return the "real" Class of the real subject
*/
public Class getObjectsRealClass()
{
return m_objectsRealClass;
}
/**
* Set the objects real class
*/
public void setObjectsRealClass(final Class objectsRealClass)
{
this.m_objectsRealClass = objectsRealClass;
}
/**
* Return a serialized Identity as byte[].
* @see #fromByteArray
*/
public byte[] serialize() throws PersistenceBrokerException
{
// Identity is serialized and written to an ObjectOutputStream
// This ObjectOutputstream is compressed by a GZIPOutputStream
// and finally written to a ByteArrayOutputStream.
// the resulting byte[] is returned
try
{
final ByteArrayOutputStream bao = new ByteArrayOutputStream();
final GZIPOutputStream gos = new GZIPOutputStream(bao);
final ObjectOutputStream oos = new ObjectOutputStream(gos);
oos.writeObject(this);
oos.close();
gos.close();
bao.close();
final byte[] result = bao.toByteArray();
return result;
}
catch (Exception ignored)
{
throw new PersistenceBrokerException(ignored);
}
}
/**
* return a String representation.
* @return java.lang.String
*/
public String toString()
{
if (m_stringRepresentation == null)
{
final StringBuffer buf = new StringBuffer();
buf.append(m_objectsTopLevelClass.getName());
for (int i = 0; i < m_pkValues.length; i++)
{
buf.append((i == 0) ? "{" : ",");
buf.append(m_pkValues[i]);
}
buf.append("}");
m_stringRepresentation = buf.toString();
}
return m_stringRepresentation;
}
/**
* OJB can handle only classes that declare at least one primary key attribute,
* this method checks this condition.
* @exception ClassNotPersistenceCapableException thrown if no primary key is
* specified for the objects class
*/
protected void checkForPrimaryKeys(final Object realObject) throws ClassNotPersistenceCapableException
{
// if no PKs are specified OJB can't handle this class !
if (m_pkValues == null || m_pkValues.length == 0)
{
throw createException("OJB needs at least one primary key attribute for class: ", realObject, null);
}
if(m_pkValues[0] instanceof ValueContainer)
throw new OJBRuntimeException("Can't handle pk values of type "+ValueContainer.class.getName());
}
/**
* return the list of Primary Key Values of the real subject
* @return Object[]
*/
public Object[] getPrimaryKeyValues()
{
return m_pkValues;
}
/**
* Compare this Identity object to any other object. This comparison is delegated
* to the super-class.
*/
public boolean equals(final Object obj)
{
if(this == obj) return true;
boolean result = false;
if (obj instanceof org.apache.ojb.broker.Identity)
{
final Identity id = (Identity) obj;
final Object[] otherPkValues = id.getPrimaryKeyValues();
result = getObjectsTopLevelClass().equals(id.getObjectsTopLevelClass())
&& (m_pkValues.length == otherPkValues.length);
for (int i = 0; result && i < m_pkValues.length; i++)
{
result = (m_pkValues[i] == null) ? (otherPkValues[i] == null)
: m_pkValues[i].equals(otherPkValues[i]);
// special treatment for byte[]
if (!result && m_pkValues[i] instanceof byte[] && otherPkValues[i] instanceof byte[])
{
result = Arrays.equals((byte[]) m_pkValues[i], (byte[]) otherPkValues[i]);
}
}
}
return result;
}
/**
* Calculate a hashcode for this Identity. The Hashcode should be
* equal for Identities where Identity.equals() returns true. Therefore
* all primary key values that return a hashcode depending on its content
* (this is assumed for String and Number) are taken into account. Additionally
* the hashCode() of the top-level class name of this Identity is representing
* is taken into account.
*/
public int hashCode()
{
/*
arminw:
identity is quasi immutable (toplevel class and PK fields
never change), thus we can note hashCode
*/
if(m_hashCode == null)
{
final HashCodeBuilder hb = new HashCodeBuilder();
for (int i = 0; i < m_pkValues.length; i++)
{
hb.append(m_pkValues[i]);
}
/*
the hashcode of different objects to be unique across different
JVM and have to be the same for the same object in different JVM,
so we can't use
hb.append(getObjectsTopLevelClass().hashCode());
because Class identity is not same in different JVM
*/
hb.append(getObjectsTopLevelClass().getName());
m_hashCode = new Integer(hb.toHashCode());
}
return m_hashCode.intValue();
}
private ClassNotPersistenceCapableException createException(String msg, final Object objectToIdentify, final Exception e)
{
final String eol = SystemUtils.LINE_SEPARATOR;
if(msg == null)
{
msg = "Unexpected error:";
}
if(e != null)
{
return new ClassNotPersistenceCapableException(msg + eol +
"objectTopLevelClass=" + (m_objectsTopLevelClass != null ? m_objectsTopLevelClass.getName() : null) + eol +
"objectRealClass=" + (m_objectsRealClass != null ? m_objectsRealClass.getName() : null) + eol +
"pkValues=" + (m_pkValues != null ? ArrayUtils.toString(m_pkValues) : null) +
(objectToIdentify != null ? (eol + "object to identify: " + objectToIdentify) : ""), e);
}
else
{
return new ClassNotPersistenceCapableException(msg + eol +
"objectTopLevelClass=" + (m_objectsTopLevelClass != null ? m_objectsTopLevelClass.getName() : null) + eol +
"objectRealClass=" + (m_objectsRealClass != null ? m_objectsRealClass.getName() : null) + eol +
"pkValues=" + (m_pkValues != null ? ArrayUtils.toString(m_pkValues) : null) +
eol + "object to identify: " + objectToIdentify);
}
}
}