package org.apache.ojb.broker.util;
/* 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.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.iterators.ArrayIterator;
import org.apache.commons.collections.map.ReferenceIdentityMap;
import org.apache.ojb.broker.Identity;
import org.apache.ojb.broker.ManageableCollection;
import org.apache.ojb.broker.MtoNImplementor;
import org.apache.ojb.broker.OJBRuntimeException;
import org.apache.ojb.broker.PBKey;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.accesslayer.StatementManagerIF;
import org.apache.ojb.broker.accesslayer.sql.SqlExistStatement;
import org.apache.ojb.broker.core.PersistenceBrokerImpl;
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.metadata.ClassDescriptor;
import org.apache.ojb.broker.metadata.CollectionDescriptor;
import org.apache.ojb.broker.metadata.FieldDescriptor;
import org.apache.ojb.broker.metadata.FieldHelper;
import org.apache.ojb.broker.metadata.MetadataException;
import org.apache.ojb.broker.metadata.MetadataManager;
import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
import org.apache.ojb.broker.platforms.Platform;
import org.apache.ojb.broker.query.Criteria;
import org.apache.ojb.broker.query.MtoNQuery;
import org.apache.ojb.broker.query.Query;
import org.apache.ojb.broker.query.QueryByCriteria;
import org.apache.ojb.broker.query.QueryBySQL;
import org.apache.ojb.broker.query.ReportQueryByCriteria;
import org.apache.ojb.broker.query.ReportQueryByMtoNCriteria;
import org.apache.ojb.broker.util.logging.LoggerFactory;
import org.apache.ojb.broker.util.sequence.SequenceManagerException;
/**
* This class contains helper methods primarily used by the {@link org.apache.ojb.broker.PersistenceBroker}
* implementation (e.g. contains methods to assign the the values of 'autoincrement' fields).
* <br/>
* Furthermore it was used to introduce new features related to {@link org.apache.ojb.broker.PersistenceBroker} - these
* new features and services (if they stand the test of time) will be moved to separate services in future.
*
* @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
* @version $Id: BrokerHelper.java,v 1.57.2.23 2005/12/21 22:27:47 tomdz Exp $
*/
public class BrokerHelper
{
public static final String REPOSITORY_NAME_SEPARATOR = "#";
private PersistenceBrokerImpl m_broker;
public BrokerHelper(PersistenceBrokerImpl broker)
{
this.m_broker = broker;
}
/**
* splits up the name string and extract db url,
* user name and password and build a new PBKey
* instance - the token '#' is used to separate
* the substrings.
* @throws PersistenceBrokerException if given name was <code>null</code>
*/
public static PBKey extractAllTokens(String name)
{
if(name == null)
{
throw new PersistenceBrokerException("Could not extract PBKey, given argument is 'null'");
}
String user = null;
String passwd = null;
StringTokenizer tok = new StringTokenizer(name, REPOSITORY_NAME_SEPARATOR);
String dbName = tok.nextToken();
if(tok.hasMoreTokens())
{
user = tok.nextToken();
if(user != null && user.trim().equals(""))
{
user = null;
}
}
if(tok.hasMoreTokens())
{
if(user != null)
passwd = tok.nextToken();
}
if(user != null && passwd == null)
{
passwd = "";
}
return new PBKey(dbName, user, passwd);
}
/**
* Check if the user of the given PBKey was <code>null</code>, if so we try to
* get user/password from the jdbc-connection-descriptor matching the given
* PBKey.getAlias().
*/
public static PBKey crossCheckPBKey(PBKey key)
{
if(key.getUser() == null)
{
PBKey defKey = MetadataManager.getInstance().connectionRepository().getStandardPBKeyForJcdAlias(key.getAlias());
if(defKey != null)
{
return defKey;
}
}
return key;
}
/**
* Answer the real ClassDescriptor for anObj
* ie. aCld may be an Interface of anObj, so the cld for anObj is returned
*/
private ClassDescriptor getRealClassDescriptor(ClassDescriptor aCld, Object anObj)
{
ClassDescriptor result;
if(aCld.getClassOfObject() == ProxyHelper.getRealClass(anObj))
{
result = aCld;
}
else
{
result = aCld.getRepository().getDescriptorFor(anObj.getClass());
}
return result;
}
/**
* Returns an Array with an Objects PK VALUES if convertToSql is true, any
* associated java-to-sql conversions are applied. If the Object is a Proxy
* or a VirtualProxy NO conversion is necessary.
*
* @param objectOrProxy
* @param convertToSql
* @return Object[]
* @throws PersistenceBrokerException
*/
public ValueContainer[] getKeyValues(ClassDescriptor cld, Object objectOrProxy, boolean convertToSql) throws PersistenceBrokerException
{
IndirectionHandler handler = ProxyHelper.getIndirectionHandler(objectOrProxy);
if(handler != null)
{
return getKeyValues(cld, handler.getIdentity(), convertToSql); //BRJ: convert Identity
}
else
{
ClassDescriptor realCld = getRealClassDescriptor(cld, objectOrProxy);
return getValuesForObject(realCld.getPkFields(), objectOrProxy, convertToSql);
}
}
/**
* Return primary key values of given Identity object.
*
* @param cld
* @param oid
* @return Object[]
* @throws PersistenceBrokerException
*/
public ValueContainer[] getKeyValues(ClassDescriptor cld, Identity oid) throws PersistenceBrokerException
{
return getKeyValues(cld, oid, true);
}
/**
* Return key Values of an Identity
* @param cld
* @param oid
* @param convertToSql
* @return Object[]
* @throws PersistenceBrokerException
*/
public ValueContainer[] getKeyValues(ClassDescriptor cld, Identity oid, boolean convertToSql) throws PersistenceBrokerException
{
FieldDescriptor[] pkFields = cld.getPkFields();
ValueContainer[] result = new ValueContainer[pkFields.length];
Object[] pkValues = oid.getPrimaryKeyValues();
try
{
for(int i = 0; i < result.length; i++)
{
FieldDescriptor fd = pkFields[i];
Object cv = pkValues[i];
if(convertToSql)
{
// BRJ : apply type and value mapping
cv = fd.getFieldConversion().javaToSql(cv);
}
result[i] = new ValueContainer(cv, fd.getJdbcType());
}
}
catch(Exception e)
{
throw new PersistenceBrokerException("Can't generate primary key values for given Identity " + oid, e);
}
return result;
}
/**
* returns an Array with an Objects PK VALUES, with any java-to-sql
* FieldConversion applied. If the Object is a Proxy or a VirtualProxy NO
* conversion is necessary.
*
* @param objectOrProxy
* @return Object[]
* @throws PersistenceBrokerException
*/
public ValueContainer[] getKeyValues(ClassDescriptor cld, Object objectOrProxy) throws PersistenceBrokerException
{
return getKeyValues(cld, objectOrProxy, true);
}
/**
* Decide if the given object value represents 'null'.<br/>
*
* - If given value is 'null' itself, true will be returned<br/>
*
* - If given value is instance of Number with value 0 and the field-descriptor
* represents a primitive field, true will be returned<br/>
*
* - If given value is instance of String with length 0 and the field-descriptor
* is a primary key, true will be returned<br/>
*/
public boolean representsNull(FieldDescriptor fld, Object aValue)
{
if(aValue == null) return true;
boolean result = false;
if(((aValue instanceof Number) && (((Number) aValue).longValue() == 0)))
{
Class type = fld.getPersistentField().getType();
/*
AnonymousPersistentFields will *always* have a null type according to the
javadoc comments in AnonymousPersistentField.getType() and never represents
a primitve java field with value 0, thus we return always 'false' in this case.
(If the value object is null, the first check above return true)
*/
if(type != null)
{
result = type.isPrimitive();
}
}
// TODO: Do we need this check?? String could be nullified, why should we assume
// it's 'null' on empty string?
else if((aValue instanceof String) && (((String) aValue).length() == 0))
{
result = fld.isPrimaryKey();
}
return result;
}
/**
* Detect if the given object has a PK field represents a 'null' value.
*/
public boolean hasNullPKField(ClassDescriptor cld, Object obj)
{
FieldDescriptor[] fields = cld.getPkFields();
boolean hasNull = false;
// an unmaterialized proxy object can never have nullified PK's
IndirectionHandler handler = ProxyHelper.getIndirectionHandler(obj);
if(handler == null || handler.alreadyMaterialized())
{
if(handler != null) obj = handler.getRealSubject();
FieldDescriptor fld;
for(int i = 0; i < fields.length; i++)
{
fld = fields[i];
hasNull = representsNull(fld, fld.getPersistentField().get(obj));
if(hasNull) break;
}
}
return hasNull;
}
/**
* Set an autoincremented value in given object field that has already
* had a field conversion run on it, if an value for the given field is
* already set, it will be overridden - no further checks are done.
* <p>
* The data type of the value that is returned by this method is
* compatible with the java-world. The return value has <b>NOT</b>
* been run through a field conversion and converted to a corresponding
* sql-type.
*
* @return the autoincremented value set on given object
* @throws PersistenceBrokerException if there is an erros accessing obj field values
*/
private Object setAutoIncrementValue(FieldDescriptor fd, Object obj)
{
PersistentField f = fd.getPersistentField();
try
{
// lookup SeqMan for a value matching db column an
Object result = m_broker.serviceSequenceManager().getUniqueValue(fd);
// reflect autoincrement value back into object
f.set(obj, result);
return result;
}
catch(MetadataException e)
{
throw new PersistenceBrokerException(
"Error while trying to autoincrement field " + f.getDeclaringClass() + "#" + f.getName(),
e);
}
catch(SequenceManagerException e)
{
throw new PersistenceBrokerException("Could not get key value", e);
}
}
/**
* Get the values of the fields for an obj
* Autoincrement values are automatically set.
* @param fields
* @param obj
* @throws PersistenceBrokerException
*/
public ValueContainer[] getValuesForObject(FieldDescriptor[] fields, Object obj, boolean convertToSql, boolean assignAutoincrement) throws PersistenceBrokerException
{
ValueContainer[] result = new ValueContainer[fields.length];
for(int i = 0; i < fields.length; i++)
{
FieldDescriptor fd = fields[i];
Object cv = fd.getPersistentField().get(obj);
/*
handle autoincrement attributes if
- is a autoincrement field
- field represents a 'null' value, is nullified
and generate a new value
*/
if(assignAutoincrement && fd.isAutoIncrement() && representsNull(fd, cv))
{
/*
setAutoIncrementValue returns a value that is
properly typed for the java-world. This value
needs to be converted to it's corresponding
sql type so that the entire result array contains
objects that are properly typed for sql.
*/
cv = setAutoIncrementValue(fd, obj);
}
if(convertToSql)
{
// apply type and value conversion
cv = fd.getFieldConversion().javaToSql(cv);
}
// create ValueContainer
result[i] = new ValueContainer(cv, fd.getJdbcType());
}
return result;
}
public ValueContainer[] getValuesForObject(FieldDescriptor[] fields, Object obj, boolean convertToSql) throws PersistenceBrokerException
{
return getValuesForObject(fields, obj, convertToSql, false);
}
/**
* Returns an array containing values for all non PK field READ/WRITE attributes of the object
* based on the specified {@link org.apache.ojb.broker.metadata.ClassDescriptor}.
* <br/>
* NOTE: This method doesn't do any checks on the specified {@link org.apache.ojb.broker.metadata.ClassDescriptor}
* the caller is reponsible to pass a valid descriptor.
*
* @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} to extract the RW-fields
* @param obj The object with target fields to extract.
* @throws MetadataException if there is an erros accessing obj field values
*/
public ValueContainer[] getNonKeyRwValues(ClassDescriptor cld, Object obj) throws PersistenceBrokerException
{
return getValuesForObject(cld.getNonPkRwFields(), obj, true);
}
/**
* Returns an array containing values for all READ/WRITE attributes of the object
* based on the specified {@link org.apache.ojb.broker.metadata.ClassDescriptor}.
* <br/>
* NOTE: This method doesn't do any checks on the specified {@link org.apache.ojb.broker.metadata.ClassDescriptor}
* the caller is reponsible to pass a valid descriptor.
*
* @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} to extract the RW-fields
* @param obj The object with target fields to extract.
* @throws MetadataException if there is an erros accessing obj field values
*/
public ValueContainer[] getAllRwValues(ClassDescriptor cld, Object obj) throws PersistenceBrokerException
{
return getValuesForObject(cld.getAllRwFields(), obj, true);
}
/**
* Extract an value array of the given {@link ValueContainer} array.
* @param containers
* @return An object array
*/
public Object[] extractValueArray(ValueContainer[] containers)
{
Object[] result = new Object[containers.length];
for(int i = 0; i < containers.length; i++)
{
result[i] = containers[i].getValue();
}
return result;
}
/**
* returns true if the primary key fields are valid for store, else false.
* PK fields are valid if each of them is either an OJB managed
* attribute (autoincrement or locking) or if it contains
* a valid non-null value
* @param fieldDescriptors the array of PK fielddescriptors
* @param pkValues the array of PK values
* @return boolean
*/
public boolean assertValidPksForStore(FieldDescriptor[] fieldDescriptors, Object[] pkValues)
{
int fieldDescriptorSize = fieldDescriptors.length;
for(int i = 0; i < fieldDescriptorSize; i++)
{
FieldDescriptor fld = fieldDescriptors[i];
/**
* a pk field is valid if it is either managed by OJB
* (autoincrement or locking) or if it does contain a
* valid non-null value.
*/
if(!(fld.isAutoIncrement()
|| fld.isLocking()
|| !representsNull(fld, pkValues[i])))
{
return false;
}
}
return true;
}
/**
* returns true if the primary key fields are valid for delete, else false.
* PK fields are valid if each of them contains a valid non-null value
* @param cld the ClassDescriptor
* @param obj the object
* @return boolean
*/
public boolean assertValidPkForDelete(ClassDescriptor cld, Object obj)
{
if(!ProxyHelper.isProxy(obj))
{
FieldDescriptor fieldDescriptors[] = cld.getPkFields();
int fieldDescriptorSize = fieldDescriptors.length;
for(int i = 0; i < fieldDescriptorSize; i++)
{
FieldDescriptor fd = fieldDescriptors[i];
Object pkValue = fd.getPersistentField().get(obj);
if (representsNull(fd, pkValue))
{
return false;
}
}
}
return true;
}
/**
* Build a Count-Query based on aQuery
* @param aQuery
* @return The count query
*/
public Query getCountQuery(Query aQuery)
{
if(aQuery instanceof QueryBySQL)
{
return getQueryBySqlCount((QueryBySQL) aQuery);
}
else if(aQuery instanceof ReportQueryByCriteria)
{
return getReportQueryByCriteriaCount((ReportQueryByCriteria) aQuery);
}
else
{
return getQueryByCriteriaCount((QueryByCriteria) aQuery);
}
}
/**
* Create a Count-Query for QueryBySQL
*
* @param aQuery
* @return The count query
*/
private Query getQueryBySqlCount(QueryBySQL aQuery)
{
String countSql = aQuery.getSql();
int fromPos = countSql.toUpperCase().indexOf(" FROM ");
if(fromPos >= 0)
{
countSql = "select count(*)" + countSql.substring(fromPos);
}
int orderPos = countSql.toUpperCase().indexOf(" ORDER BY ");
if(orderPos >= 0)
{
countSql = countSql.substring(0, orderPos);
}
return new QueryBySQL(aQuery.getSearchClass(), countSql);
}
/**
* Create a Count-Query for QueryByCriteria
*/
private Query getQueryByCriteriaCount(QueryByCriteria aQuery)
{
Class searchClass = aQuery.getSearchClass();
ReportQueryByCriteria countQuery = null;
Criteria countCrit = null;
String[] columns = new String[1];
// BRJ: copied Criteria without groupby, orderby, and prefetched relationships
if (aQuery.getCriteria() != null)
{
countCrit = aQuery.getCriteria().copy(false, false, false);
}
if (aQuery.isDistinct())
{
// BRJ: Count distinct is dbms dependent
// hsql/sapdb: select count (distinct(person_id || project_id)) from person_project
// mysql: select count (distinct person_id,project_id) from person_project
// [tomdz]
// Some databases have no support for multi-column count distinct (e.g. Derby)
// Here we use a SELECT count(*) FROM (SELECT DISTINCT ...) instead
//
// concatenation of pk-columns is a simple way to obtain a single column
// but concatenation is also dbms dependent:
//
// SELECT count(distinct concat(row1, row2, row3)) mysql
// SELECT count(distinct (row1 || row2 || row3)) ansi
// SELECT count(distinct (row1 + row2 + row3)) ms sql-server
FieldDescriptor[] pkFields = m_broker.getClassDescriptor(searchClass).getPkFields();
String[] keyColumns = new String[pkFields.length];
if (pkFields.length > 1)
{
// TODO: Use ColumnName. This is a temporary solution because
// we cannot yet resolve multiple columns in the same attribute.
for (int idx = 0; idx < pkFields.length; idx++)
{
keyColumns[idx] = pkFields[idx].getColumnName();
}
}
else
{
for (int idx = 0; idx < pkFields.length; idx++)
{
keyColumns[idx] = pkFields[idx].getAttributeName();
}
}
// [tomdz]
// TODO: Add support for databases that do not support COUNT DISTINCT over multiple columns
// if (getPlatform().supportsMultiColumnCountDistinct())
// {
// columns[0] = "count(distinct " + getPlatform().concatenate(keyColumns) + ")";
// }
// else
// {
// columns = keyColumns;
// }
columns[0] = "count(distinct " + getPlatform().concatenate(keyColumns) + ")";
}
else
{
columns[0] = "count(*)";
}
// BRJ: we have to preserve indirection table !
if (aQuery instanceof MtoNQuery)
{
MtoNQuery mnQuery = (MtoNQuery)aQuery;
ReportQueryByMtoNCriteria mnReportQuery = new ReportQueryByMtoNCriteria(searchClass, columns, countCrit);
mnReportQuery.setIndirectionTable(mnQuery.getIndirectionTable());
countQuery = mnReportQuery;
}
else
{
countQuery = new ReportQueryByCriteria(searchClass, columns, countCrit);
}
// BRJ: we have to preserve outer-join-settings (by Andr� Markwalder)
for (Iterator outerJoinPath = aQuery.getOuterJoinPaths().iterator(); outerJoinPath.hasNext();)
{
String path = (String) outerJoinPath.next();
if (aQuery.isPathOuterJoin(path))
{
countQuery.setPathOuterJoin(path);
}
}
//BRJ: add orderBy Columns asJoinAttributes
List orderBy = aQuery.getOrderBy();
if ((orderBy != null) && !orderBy.isEmpty())
{
String[] joinAttributes = new String[orderBy.size()];
for (int idx = 0; idx < orderBy.size(); idx++)
{
joinAttributes[idx] = ((FieldHelper)orderBy.get(idx)).name;
}
countQuery.setJoinAttributes(joinAttributes);
}
// [tomdz]
// TODO:
// For those databases that do not support COUNT DISTINCT over multiple columns
// we wrap the normal SELECT DISTINCT that we just created, into a SELECT count(*)
// For this however we need a report query that gets its data from a sub query instead
// of a table (target class)
// if (aQuery.isDistinct() && !getPlatform().supportsMultiColumnCountDistinct())
// {
// }
return countQuery;
}
/**
* Create a Count-Query for ReportQueryByCriteria
*/
private Query getReportQueryByCriteriaCount(ReportQueryByCriteria aQuery)
{
ReportQueryByCriteria countQuery = (ReportQueryByCriteria) getQueryByCriteriaCount(aQuery);
// BRJ: keep the original columns to build the Join
countQuery.setJoinAttributes(aQuery.getAttributes());
// BRJ: we have to preserve groupby information
Iterator iter = aQuery.getGroupBy().iterator();
while(iter.hasNext())
{
countQuery.addGroupBy((FieldHelper) iter.next());
}
return countQuery;
}
/**
* answer the platform
*
* @return the platform
*/
private Platform getPlatform()
{
return m_broker.serviceSqlGenerator().getPlatform();
}
/*
NOTE: use weak key references to allow reclaiming
of no longer used ClassDescriptor instances
*/
private Map sqlSelectMap = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.HARD);
/**
* TODO: This method should be moved to {@link org.apache.ojb.broker.accesslayer.JdbcAccess}
* before 1.1 release.
*
* This method checks if the requested object can be
* found in database (without object materialization).
*
* @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} of the
* object/{@link org.apache.ojb.broker.Identity} to check.
* @param obj The <em>object</em> to check.
* @param oid The associated {@link org.apache.ojb.broker.Identity}.
* {@link org.apache.ojb.broker.Identity} of the object
* @return Return <em>true</em> if the object is already persisted, <em>false</em> if the object is transient.
*/
public boolean doesExist(ClassDescriptor cld, Identity oid, Object obj)
{
boolean result = false;
String sql = (String) sqlSelectMap.get(cld);
if(sql == null)
{
sql = new SqlExistStatement(cld, LoggerFactory.getDefaultLogger()).getStatement();
sqlSelectMap.put(cld, sql);
}
ValueContainer[] pkValues;
if(oid == null)
{
pkValues = getKeyValues(cld, obj, true);
}
else
{
pkValues = getKeyValues(cld, oid);
}
StatementManagerIF sm = m_broker.serviceStatementManager();
PreparedStatement stmt = null;
ResultSet rs = null;
try
{
stmt = sm.getPreparedStatement(cld, sql, false, 1, false);
sm.bindValues(stmt, pkValues, 1);
rs = stmt.executeQuery();
result = rs.next();
}
catch(SQLException e)
{
throw ExceptionHelper.generateException("[BrokerHelper#doesExist] Can't check if specified" +
" object is already persisted", e, sql, cld, pkValues, null, obj);
}
finally
{
sm.closeResources(stmt, rs);
}
return result;
}
/**
* This method concatenate the main object with all reference
* objects (1:1, 1:n and m:n) by hand. This method is needed when
* in the reference metadata definitions the auto-xxx setting was disabled.
* More info see OJB doc.
*/
public void link(Object obj, boolean insert)
{
linkOrUnlink(true, obj, insert);
}
/**
* Unlink all references from this object.
* More info see OJB doc.
* @param obj Object with reference
*/
public void unlink(Object obj)
{
linkOrUnlink(false, obj, false);
}
private void linkOrUnlink(boolean doLink, Object obj, boolean insert)
{
ClassDescriptor cld = m_broker.getDescriptorRepository().getDescriptorFor(obj.getClass());
if (cld.getObjectReferenceDescriptors().size() > 0)
{
// never returns null, thus we can direct call iterator
Iterator descriptors = cld.getObjectReferenceDescriptors().iterator();
while (descriptors.hasNext())
{
ObjectReferenceDescriptor ord = (ObjectReferenceDescriptor) descriptors.next();
linkOrUnlinkOneToOne(doLink, obj, ord, insert);
}
}
if (cld.getCollectionDescriptors().size() > 0)
{
// never returns null, thus we can direct call iterator
Iterator descriptors = cld.getCollectionDescriptors().iterator();
while (descriptors.hasNext())
{
CollectionDescriptor cod = (CollectionDescriptor) descriptors.next();
linkOrUnlinkXToMany(doLink, obj, cod, insert);
}
}
}
/**
* This method concatenate the main object and the specified reference
* object (1:1 reference a referenced object, 1:n and m:n reference a
* collection of referenced objects) by hand. This method is needed when
* in the reference metadata definitions the auto-xxx setting was disabled.
* More info see OJB doc.
*
* @param obj Object with reference
* @param ord the ObjectReferenceDescriptor of the reference
* @param insert flag signals insert operation
*/
public void link(Object obj, ObjectReferenceDescriptor ord, boolean insert)
{
linkOrUnlink(true, obj, ord, insert);
}
/**
* This method concatenate the main object and the specified reference
* object (1:1 reference a referenced object, 1:n and m:n reference a
* collection of referenced objects) by hand. This method is needed when
* in the reference metadata definitions the auto-xxx setting was disabled.
* More info see OJB doc.
*
* @param obj Object with reference
* @param attributeName field name of the reference
* @param insert flag signals insert operation
* @return true if the specified reference was found and linking was successful
*/
public boolean link(Object obj, String attributeName, boolean insert)
{
return linkOrUnlink(true, obj, attributeName, insert);
}
/**
* This method concatenate the main object and the specified reference
* object (1:1 reference a referenced object, 1:n and m:n reference a
* collection of referenced objects) by hand. This method is needed when
* in the reference metadata definitions the auto-xxx setting was disabled.
* More info see OJB doc.
*
* @param obj Object with reference
* @param attributeName field name of the reference
* @param reference The referenced object
* @param insert flag signals insert operation
* @return true if the specified reference was found and linking was successful
*/
public boolean link(Object obj, String attributeName, Object reference, boolean insert)
{
ClassDescriptor cld = m_broker.getDescriptorRepository().getDescriptorFor(ProxyHelper.getRealClass(obj));
ObjectReferenceDescriptor ord;
boolean match = false;
// first look for reference then for collection
ord = cld.getObjectReferenceDescriptorByName(attributeName);
if (ord != null)
{
linkOrUnlinkOneToOne(true, obj, ord, insert);
match = true;
}
else
{
CollectionDescriptor cod = cld.getCollectionDescriptorByName(attributeName);
if (cod != null)
{
linkOrUnlinkXToMany(true, obj, cod, insert);
match = true;
}
}
return match;
}
/**
* Unlink the specified reference object.
* More info see OJB doc.
* @param source The source object with the specified reference field.
* @param attributeName The field name of the reference to unlink.
* @param target The referenced object to unlink.
*/
public boolean unlink(Object source, String attributeName, Object target)
{
return linkOrUnlink(false, source, attributeName, false);
}
/**
* Unlink all referenced objects of the specified field.
* More info see OJB doc.
* @param source The source object with the specified reference.
* @param attributeName The field name of the reference to unlink.
*/
public boolean unlink(Object source, String attributeName)
{
return linkOrUnlink(false, source, attributeName, false);
}
/**
* Unlink the specified reference from this object.
* More info see OJB doc.
*
* @param obj Object with reference
* @param ord the ObjectReferenceDescriptor of the reference
* @param insert flag signals insert operation
*/
public void unlink(Object obj, ObjectReferenceDescriptor ord, boolean insert)
{
linkOrUnlink(false, obj, ord, insert);
}
private boolean linkOrUnlink(boolean doLink, Object obj, String attributeName, boolean insert)
{
boolean match = false;
ClassDescriptor cld = m_broker.getDescriptorRepository().getDescriptorFor(ProxyHelper.getRealClass(obj));
ObjectReferenceDescriptor ord;
// first look for reference then for collection
ord = cld.getObjectReferenceDescriptorByName(attributeName);
if (ord != null)
{
linkOrUnlinkOneToOne(doLink, obj, ord, insert);
match = true;
}
else
{
CollectionDescriptor cod = cld.getCollectionDescriptorByName(attributeName);
if (cod != null)
{
linkOrUnlinkXToMany(doLink, obj, cod, insert);
match = true;
}
}
return match;
}
private void linkOrUnlink(boolean doLink, Object obj, ObjectReferenceDescriptor ord, boolean insert)
{
if (ord instanceof CollectionDescriptor)
{
linkOrUnlinkXToMany(doLink, obj, (CollectionDescriptor) ord, insert);
}
else
{
linkOrUnlinkOneToOne(doLink, obj, ord, insert);
}
}
private void linkOrUnlinkXToMany(boolean doLink, Object obj, CollectionDescriptor cod, boolean insert)
{
if (doLink)
{
if (cod.isMtoNRelation())
{
m_broker.linkMtoN(obj, cod, insert);
}
else
{
m_broker.linkOneToMany(obj, cod, insert);
}
}
else
{
m_broker.unlinkXtoN(obj, cod);
}
}
private void linkOrUnlinkOneToOne(boolean doLink, Object obj, ObjectReferenceDescriptor ord, boolean insert)
{
/*
arminw: we need the class-descriptor where the reference is declared, thus we ask the
reference-descriptor for this, instead of using the class-descriptor of the specified
object. If the reference was declared within an interface (should never happen) we
only can use the descriptor of the real class.
*/
ClassDescriptor cld = ord.getClassDescriptor();
if(cld.isInterface())
{
cld = m_broker.getDescriptorRepository().getDescriptorFor(ProxyHelper.getRealClass(obj));
}
if (doLink)
{
m_broker.linkOneToOne(obj, cld, ord, insert);
}
else
{
m_broker.unlinkFK(obj, cld, ord);
// in 1:1 relation we have to set relation to null
ord.getPersistentField().set(obj, null);
}
}
/**
* Unlink a bunch of 1:n or m:n objects.
*
* @param source The source object with reference.
* @param cds The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} of the relation.
* @param referencesToUnlink List of referenced objects to unlink.
*/
public void unlink(Object source, CollectionDescriptor cds, List referencesToUnlink)
{
for(int i = 0; i < referencesToUnlink.size(); i++)
{
unlink(source, cds, referencesToUnlink.get(i));
}
}
/**
* Unlink a single 1:n or m:n object.
*
* @param source The source object with reference.
* @param cds The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} of the relation.
* @param referenceToUnlink The referenced object to link.
*/
public void unlink(Object source, CollectionDescriptor cds, Object referenceToUnlink)
{
if(cds.isMtoNRelation())
{
m_broker.deleteMtoNImplementor(new MtoNImplementor(cds, source, referenceToUnlink));
}
else
{
ClassDescriptor cld = m_broker.getClassDescriptor(referenceToUnlink.getClass());
m_broker.unlinkFK(referenceToUnlink, cld, cds);
}
}
/**
* Link a bunch of 1:n or m:n objects.
*
* @param source The source object with reference.
* @param cds The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} of the relation.
* @param referencesToLink List of referenced objects to link.
*/
public void link(Object source, CollectionDescriptor cds, List referencesToLink)
{
for(int i = 0; i < referencesToLink.size(); i++)
{
link(source, cds, referencesToLink.get(i));
}
}
/**
* Link a single 1:n or m:n object.
*
* @param source The source object with the declared reference.
* @param cds The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} of the relation declared in source object.
* @param referenceToLink The referenced object to link.
*/
public void link(Object source, CollectionDescriptor cds, Object referenceToLink)
{
if(cds.isMtoNRelation())
{
m_broker.addMtoNImplementor(new MtoNImplementor(cds, source, referenceToLink));
}
else
{
ClassDescriptor cld = m_broker.getClassDescriptor(referenceToLink.getClass());
m_broker.link(referenceToLink, cld, cds, source, false);
}
}
/**
* Returns an Iterator instance for {@link java.util.Collection}, object Array or
* {@link org.apache.ojb.broker.ManageableCollection} instances.
*
* @param collectionOrArray a none <em>null</em> object of type {@link java.util.Collection},
* Array or {@link org.apache.ojb.broker.ManageableCollection}.
* @return Iterator able to handle given collection object
*/
public static Iterator getCollectionIterator(Object collectionOrArray)
{
Iterator colIterator;
if (collectionOrArray instanceof ManageableCollection)
{
colIterator = ((ManageableCollection) collectionOrArray).ojbIterator();
}
else if (collectionOrArray instanceof Collection)
{
colIterator = ((Collection) collectionOrArray).iterator();
}
else if (collectionOrArray.getClass().isArray())
{
colIterator = new ArrayIterator(collectionOrArray);
}
else
{
throw new OJBRuntimeException( "Given object collection of type '"
+ (collectionOrArray != null ? collectionOrArray.getClass().toString() : "null")
+ "' can not be managed by OJB. Use Array, Collection or ManageableCollection instead!");
}
return colIterator;
}
/**
* Returns an object array for {@link java.util.Collection}, array or
* {@link org.apache.ojb.broker.ManageableCollection} instances.
*
* @param collectionOrArray a none <em>null</em> object of type {@link java.util.Collection},
* Array or {@link org.apache.ojb.broker.ManageableCollection}.
* @return Object array able to handle given collection or array object
*/
public static Object[] getCollectionArray(Object collectionOrArray)
{
Object[] result;
if (collectionOrArray instanceof Collection)
{
result = ((Collection) collectionOrArray).toArray();
}
else if (collectionOrArray instanceof ManageableCollection)
{
Collection newCol = new ArrayList();
CollectionUtils.addAll(newCol, ((ManageableCollection) collectionOrArray).ojbIterator());
result = newCol.toArray();
}
else if (collectionOrArray.getClass().isArray())
{
result = (Object[]) collectionOrArray;
}
else
{
throw new OJBRuntimeException( "Given object collection of type '"
+ (collectionOrArray != null ? collectionOrArray.getClass().toString() : "null")
+ "' can not be managed by OJB. Use Array, Collection or ManageableCollection instead!");
}
return result;
}
/**
* Returns <em>true</em> if one or more anonymous FK fields are used.
* @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} of the main object.
* @param rds The {@link org.apache.ojb.broker.metadata.ObjectReferenceDescriptor} of the referenced object.
* @return <em>true</em> if one or more anonymous FK fields are used for specified reference.
*/
public static boolean hasAnonymousKeyReference(ClassDescriptor cld, ObjectReferenceDescriptor rds)
{
boolean result = false;
FieldDescriptor[] fkFields = rds.getForeignKeyFieldDescriptors(cld);
for(int i = 0; i < fkFields.length; i++)
{
FieldDescriptor fkField = fkFields[i];
if(fkField.isAnonymous())
{
result = true;
break;
}
}
return result;
}
// /**
// * Use this method to extract the {@link org.apache.ojb.broker.metadata.ClassDescriptor} where
// * the {@link org.apache.ojb.broker.metadata.ObjectReferenceDescriptor reference} is declared.
// * It's possible that the reference is declared in a super-class.
// * @param broker
// * @param reference
// * @param source
// * @return
// */
// public static ClassDescriptor extractDescriptorForReference(PersistenceBroker broker, ObjectReferenceDescriptor reference, Object source)
// {
// /*
// arminw: we need the class-descriptor where the reference is declared, thus we ask the
// reference-descriptor for this, instead of using the class-descriptor of the specified
// object. If the reference was declared within an interface (should never happen) we
// only can use the descriptor of the real class.
// */
// ClassDescriptor cld = reference.getClassDescriptor();
// if(cld.isInterface())
// {
// cld = broker.getDescriptorRepository().getDescriptorFor(ProxyHelper.getRealClass(source));
// }
// return cld;
// }
// /**
// * Returns a {@link java.util.List} instance of the specified object in method argument,
// * in which the argument must be of type {@link java.util.Collection}, array or
// * {@link org.apache.ojb.broker.ManageableCollection}.
// *
// * @param collectionOrArray a none <em>null</em> object of type {@link java.util.Collection},
// * Array or {@link org.apache.ojb.broker.ManageableCollection}.
// * @return Object array able to handle given collection or array object
// */
// public static List getCollectionList(Object collectionOrArray)
// {
// List result = null;
// if (collectionOrArray instanceof Collection)
// {
// result = ((Collection) collectionOrArray).toArray();
// }
// else if (collectionOrArray instanceof ManageableCollection)
// {
// Collection newCol = new ArrayList();
// CollectionUtils.addAll(newCol, ((ManageableCollection) collectionOrArray).ojbIterator());
// result = newCol.toArray();
// }
// else if (collectionOrArray.getClass().isArray())
// {
// result = (Object[]) collectionOrArray;
// }
// else
// {
// throw new OJBRuntimeException( "Given object collection of type '"
// + (collectionOrArray != null ? collectionOrArray.getClass().toString() : "null")
// + "' can not be managed by OJB. Use Array, Collection or ManageableCollection instead!");
// }
// return result;
// }
}