package org.apache.ojb.broker.core;
/* Copyright 2002-2005 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 java.util.Map;
import org.apache.ojb.broker.Identity;
import org.apache.ojb.broker.IdentityFactory;
import org.apache.ojb.broker.OJBRuntimeException;
import org.apache.ojb.broker.PersistenceBroker;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.PBStateListener;
import org.apache.ojb.broker.PBStateEvent;
import org.apache.ojb.broker.core.proxy.IndirectionHandler;
import org.apache.ojb.broker.core.proxy.ProxyHelper;
import org.apache.ojb.broker.metadata.ClassDescriptor;
import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
import org.apache.ojb.broker.metadata.FieldDescriptor;
import org.apache.ojb.broker.util.BrokerHelper;
import org.apache.ojb.broker.util.sequence.SequenceManager;
import org.apache.ojb.broker.util.sequence.SequenceManagerTransientImpl;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.collections.map.ReferenceIdentityMap;
/**
* @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
* @version $Id: IdentityFactoryImpl.java,v 1.2.2.5 2005/12/21 22:25:01 tomdz Exp $
* @see org.apache.ojb.broker.IdentityFactory
*/
public class IdentityFactoryImpl implements IdentityFactory, PBStateListener
{
private PersistenceBroker broker;
//private boolean activeTx;
private Map objectToIdentityMap;
private SequenceManager transientSequenceManager;
public IdentityFactoryImpl(PersistenceBroker broker)
{
this.broker = broker;
this.objectToIdentityMap = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.HARD, true);
this.transientSequenceManager = new SequenceManagerTransientImpl(broker);
broker.addListener(this, true);
}
/**
* This methods creates a new transient (if at least one PK field is 'null') or persistent
* (if the PK fields are populated) {@link org.apache.ojb.broker.Identity} instance. If the specified object
* is transient and former call for the same object returns already a transient Identity, the same transient
* Identity object will be returned.
*/
protected Identity createTransientOrRealIdentity(ClassDescriptor cld, Object objOrProxy)
{
if(objOrProxy == null) throw new OJBRuntimeException("Can't create Identity for 'null'-object");
Identity result = null;
Class topLevelClass = null;
Class realClass = null;
Object[] pks = null;
try
{
final IndirectionHandler handler = ProxyHelper.getIndirectionHandler(objOrProxy);
synchronized(objOrProxy)
{
if(handler != null)
{
result = handler.getIdentity();
}
else
{
// now we are sure that the specified object is not a proxy
realClass = objOrProxy.getClass();
topLevelClass = broker.getTopLevelClass(objOrProxy.getClass());
if(cld == null)
{
cld = broker.getClassDescriptor(objOrProxy.getClass());
}
BrokerHelper helper = broker.serviceBrokerHelper();
FieldDescriptor[] fields = cld.getPkFields();
pks = new Object[fields.length];
FieldDescriptor fld;
for(int i = 0; i < fields.length; i++)
{
fld = fields[i];
/*
we check all PK fields for 'null'-values
*/
Object value = fld.getPersistentField().get(objOrProxy);
if(helper.representsNull(fld, value))
{
result = (Identity) objectToIdentityMap.get(objOrProxy);
if(result == null)
{
pks[i] = transientSequenceManager.getUniqueValue(fld);
result = new Identity(realClass, topLevelClass, pks, true);
//if(activeTx) objectToIdentityMap.put(objOrProxy, result);
objectToIdentityMap.put(objOrProxy, result);
}
break;
}
else
{
pks[i] = value;
}
}
if(result == null)
{
result = new Identity(realClass, topLevelClass, pks, false);
}
}
}
}
catch(ClassNotPersistenceCapableException e)
{
throw e;
}
catch(Exception e)
{
throw createException(e, "Can not init Identity for given object.", objOrProxy, topLevelClass, realClass, pks);
}
return result;
}
/** @see org.apache.ojb.broker.IdentityFactory#buildIdentity(Object) */
public Identity buildIdentity(Object obj)
{
return createTransientOrRealIdentity(broker.getClassDescriptor(ProxyHelper.getRealClass(obj)), obj);
}
/** @see org.apache.ojb.broker.IdentityFactory#buildIdentity(Object) */
public Identity buildIdentity(ClassDescriptor cld, Object obj)
{
return createTransientOrRealIdentity(cld, obj);
}
/** @see org.apache.ojb.broker.IdentityFactory#buildIdentity(Class, Class, String[], Object[]) */
public Identity buildIdentity(Class realClass, Class topLevelClass, String[] pkFieldNames, Object[] pkValues)
{
Object[] orderedPKValues = pkValues;
if(pkValues == null)
{
throw new NullPointerException("Given primary key value array can't be null");
}
if(pkValues.length == 1 && (pkFieldNames == null || pkFieldNames.length == 1))
{
/*
we assume only a single PK field is defined and do no further checks,
we have nothing to do
*/
}
else
{
// in other more complex cases we do several check
FieldDescriptor[] flds = broker.getClassDescriptor(realClass).getPkFields();
if(!isOrdered(flds, pkFieldNames))
{
orderedPKValues = reorderFieldValues(flds, pkFieldNames, pkValues);
}
}
return new Identity(realClass, topLevelClass, orderedPKValues);
}
/**
* This method orders the specified field values based on the
* specified {@link org.apache.ojb.broker.metadata.FieldDescriptor}.
*
* @param flds The {@link org.apache.ojb.broker.metadata.FieldDescriptor} array.
* @param fieldNames The field names.
* @param fieldValues The field values.
* @return The ordered field values.
*/
private Object[] reorderFieldValues(FieldDescriptor[] flds, String[] fieldNames, Object[] fieldValues)
{
String fieldName;
Object[] orderedValues = new Object[flds.length];
for(int i = 0; i < flds.length; i++)
{
fieldName = flds[i].getPersistentField().getName();
int realPosition = findIndexForName(fieldNames, fieldName);
orderedValues[i] = fieldValues[realPosition];
}
return orderedValues;
}
/**
* Find the index of the specified name in field name array.
*/
private int findIndexForName(String[] fieldNames, String searchName)
{
for(int i = 0; i < fieldNames.length; i++)
{
if(searchName.equals(fieldNames[i]))
{
return i;
}
}
throw new PersistenceBrokerException("Can't find field name '" + searchName +
"' in given array of field names");
}
/** Checks length and compare order of field names with declared PK fields in metadata. */
private boolean isOrdered(FieldDescriptor[] flds, String[] pkFieldNames)
{
if((flds.length > 1 && pkFieldNames == null) || flds.length != pkFieldNames.length)
{
throw new PersistenceBrokerException("pkFieldName length does not match number of defined PK fields." +
" Expected number of PK fields is " + flds.length + ", given number was " +
(pkFieldNames != null ? pkFieldNames.length : 0));
}
boolean result = true;
for(int i = 0; i < flds.length; i++)
{
FieldDescriptor fld = flds[i];
result = result && fld.getPersistentField().getName().equals(pkFieldNames[i]);
}
return result;
}
/** @see org.apache.ojb.broker.IdentityFactory#buildIdentity(Class, String[], Object[]) */
public Identity buildIdentity(Class realClass, String[] pkFieldNames, Object[] pkValues)
{
return buildIdentity(realClass, broker.getTopLevelClass(realClass), pkFieldNames, pkValues);
}
/** @see org.apache.ojb.broker.IdentityFactory#buildIdentity(Class, String[], Object[]) */
public Identity buildIdentity(Class realClass, Class topLevelClass, Object[] pkValues)
{
return new Identity(realClass, topLevelClass, pkValues);
}
/** @see org.apache.ojb.broker.IdentityFactory#buildIdentity(Class, Object) */
public Identity buildIdentity(Class realClass, Object pkValue)
{
return buildIdentity(realClass, (String[]) null, new Object[]{pkValue});
}
/**
* Helper method which supports creation of proper error messages.
*
* @param ex An exception to include or <em>null</em>.
* @param message The error message or <em>null</em>.
* @param objectToIdentify The current used object or <em>null</em>.
* @param topLevelClass The object top-level class or <em>null</em>.
* @param realClass The object real class or <em>null</em>.
* @param pks The associated PK values of the object or <em>null</em>.
* @return The generated exception.
*/
private PersistenceBrokerException createException(final Exception ex, String message, final Object objectToIdentify, Class topLevelClass, Class realClass, Object[] pks)
{
final String eol = SystemUtils.LINE_SEPARATOR;
StringBuffer msg = new StringBuffer();
if(message == null)
{
msg.append("Unexpected error: ");
}
else
{
msg.append(message).append(" :");
}
if(topLevelClass != null) msg.append(eol).append("objectTopLevelClass=").append(topLevelClass.getName());
if(realClass != null) msg.append(eol).append("objectRealClass=").append(realClass.getName());
if(pks != null) msg.append(eol).append("pkValues=").append(ArrayUtils.toString(pks));
if(objectToIdentify != null) msg.append(eol).append("object to identify: ").append(objectToIdentify);
if(ex != null)
{
// add causing stack trace
Throwable rootCause = ExceptionUtils.getRootCause(ex);
if(rootCause != null)
{
msg.append(eol).append("The root stack trace is --> ");
String rootStack = ExceptionUtils.getStackTrace(rootCause);
msg.append(eol).append(rootStack);
}
return new PersistenceBrokerException(msg.toString(), ex);
}
else
{
return new PersistenceBrokerException(msg.toString());
}
}
//===================================================================
// PBStateListener interface
//===================================================================
public void afterBegin(PBStateEvent event)
{
}
public void afterCommit(PBStateEvent event)
{
if(objectToIdentityMap.size() > 0) objectToIdentityMap.clear();
}
public void afterRollback(PBStateEvent event)
{
if(objectToIdentityMap.size() > 0) objectToIdentityMap.clear();
}
public void beforeClose(PBStateEvent event)
{
if(objectToIdentityMap.size() > 0) objectToIdentityMap.clear();
}
public void beforeRollback(PBStateEvent event)
{
}
public void afterOpen(PBStateEvent event)
{
}
public void beforeBegin(PBStateEvent event)
{
}
public void beforeCommit(PBStateEvent event)
{
}
}