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 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.1 2004/07/27 00:33:12 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(Class realClass, Class topLevel, Object[] pkValues)
{
m_objectsTopLevelClass = topLevel;
m_objectsRealClass = realClass;
m_pkValues = pkValues;
checkForPrimaryKeys();
}
public Identity(Object objectToIdentitify, PersistenceBroker targetBroker)
{
init(objectToIdentitify, targetBroker, null);
}
public Identity(Object objectToIdentitify, PersistenceBroker targetBroker, ClassDescriptor cld)
{
init(objectToIdentitify, targetBroker, cld);
}
private void init(Object objectToIdentify, PersistenceBroker targetBroker, ClassDescriptor cld)
{
if(objectToIdentify == null) throw new OJBRuntimeException("Can't create Identity for 'null'-object");
try
{
IndirectionHandler handler = ProxyHelper.getIndirectionHandler(objectToIdentify);
if (handler != null)
{
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
BrokerHelper helper = targetBroker.serviceBrokerHelper();
m_pkValues = helper.extractValueArray( helper.getKeyValues(cld, objectToIdentify, false) );
}
checkForPrimaryKeys();
}
catch (Exception e)
{
throw new ClassNotPersistenceCapableException("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(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
{
ByteArrayInputStream bais = new ByteArrayInputStream(anArray);
GZIPInputStream gis = new GZIPInputStream(bais);
ObjectInputStream ois = new ObjectInputStream(gis);
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(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
{
ByteArrayOutputStream bao = new ByteArrayOutputStream();
GZIPOutputStream gos = new GZIPOutputStream(bao);
ObjectOutputStream oos = new ObjectOutputStream(gos);
oos.writeObject(this);
oos.close();
gos.close();
bao.close();
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)
{
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() throws ClassNotPersistenceCapableException
{
// if no PKs are specified OJB can't handle this class !
if (m_pkValues.length == 0)
{
throw new ClassNotPersistenceCapableException(
"OJB needs at least one primary key attribute for class "
+ " objectsRealClass=" + m_objectsRealClass
+ ", objectTopLevelClass=" + m_objectsTopLevelClass);
}
if(m_pkValues[0] instanceof ValueContainer)
throw new RuntimeException("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(Object obj)
{
if(this == obj) return true;
boolean result = false;
if (obj instanceof org.apache.ojb.broker.Identity)
{
Identity id = (Identity) obj;
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 Class 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)
{
HashCodeBuilder hb = new HashCodeBuilder();
hb.append(getObjectsTopLevelClass().hashCode());
for (int i = 0; i < m_pkValues.length; i++)
{
hb.append(m_pkValues[i]);
}
m_hashCode = new Integer(hb.toHashCode());
}
return m_hashCode.intValue();
}
}