package org.apache.ojb.broker.metadata;
/* 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.Hashtable;
import java.util.Iterator;
import java.util.Vector;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.ojb.broker.OJBRuntimeException;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.core.proxy.ProxyHelper;
import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
/**
* Describes a Field containing a reference to another class. Provides handling for foreign keys etc.
* <br>
* Note: Be careful when use references of this class or caching instances of this class,
* because instances could become invalid (see {@link MetadataManager}).
*
* @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
*
*/
public class ObjectReferenceDescriptor extends AttributeDescriptorBase implements XmlCapable
{
private static final long serialVersionUID = 5561562217150972131L;
public static final int CASCADE_NONE = 17;
public static final int CASCADE_LINK = 19;
public static final int CASCADE_OBJECT = 23;
private Class m_ClassOfItems = null;
private Vector m_ForeignKeyFields = new Vector();
private boolean m_CascadeRetrieve = true;
private int m_CascadeStore = CASCADE_NONE;
private int m_CascadeDelete = CASCADE_NONE;
private int m_ProxyPrefetchingLimit = 50;
private Class m_ProxyOfItems = null;
private boolean m_LookedUpProxy = false;
private boolean m_OtmDependent = false;
/**
* holds the foreign-key field descriptor array for a specified class
*/
private Hashtable fkFieldMap = new Hashtable();
/**
* define loading strategy of the resulting object
*/
private boolean lazy = false;
/**
* if true relationship is refreshed when owner is found in cache
*/
private boolean refresh = false;
/**
*
*/
public ObjectReferenceDescriptor(ClassDescriptor descriptor)
{
super(descriptor);
}
/**
*
*/
public Class getItemProxyClass() throws PersistenceBrokerException
{
if (!m_LookedUpProxy)
{
m_ProxyOfItems = getClassDescriptor().getRepository().
getDescriptorFor(m_ClassOfItems).getProxyClass();
m_LookedUpProxy = true;
}
return m_ProxyOfItems;
}
/**
*
*/
public FieldDescriptor[] getForeignKeyFieldDescriptors(ClassDescriptor cld)
{
FieldDescriptor[] foreignKeyFieldDescriptors;
if ((foreignKeyFieldDescriptors = (FieldDescriptor[]) fkFieldMap.get(cld)) == null)
{
// 1. collect vector of indices of Fk-Fields
Vector v = getForeignKeyFields();
// 2. get FieldDescriptor for each index from Class-descriptor
// 2A. In a many-to-many relationship foreignkeyfields vector will be null.
if (v != null)
{
Vector ret;
if (cld.isInterface())
{
//exchange interface class descriptor with first concrete
//class
Vector extents = cld.getExtentClasses();
Class firstConcreteClass = (Class) extents.get(0);
cld = getClassDescriptor().getRepository().getDescriptorFor(firstConcreteClass);
}
ret = new Vector();
Iterator iter = v.iterator();
while (iter.hasNext())
{
Object fk = iter.next();
FieldDescriptor fkfd = null;
/*
OJB-55
it's possible that the FK field is declared in the super classes of this object,
so we can search for a valid field in super class-descriptor
*/
ClassDescriptor tmp = cld;
while(tmp != null)
{
if (fk instanceof Integer)
{
Integer index = (Integer) fk;
fkfd = cld.getFieldDescriptorByIndex(index.intValue());
}
else
{
fkfd = tmp.getFieldDescriptorByName((String) fk);
}
if(fkfd != null)
{
break;
}
else
{
tmp = tmp.getSuperClassDescriptor();
}
}
if (fkfd == null)
{
throw new OJBRuntimeException("Incorrect or not found field reference name '"
+ fk + "' in descriptor " + this + " for class-descriptor '"
+ (cld != null ? cld.getClassNameOfObject() + "'" : "'null'"));
}
ret.add(fkfd);
}
foreignKeyFieldDescriptors = (FieldDescriptor[]) ret.toArray(new FieldDescriptor[ret.size()]);
fkFieldMap.put(cld, foreignKeyFieldDescriptors);
}
}
return foreignKeyFieldDescriptors;
}
/**
* Returns an Object array of all FK field values of the specified object.
* If the specified object is an unmaterialized Proxy, it will be materialized
* to read the FK values.
*
* @throws MetadataException if an error occours while accessing ForeingKey values on obj
*/
public Object[] getForeignKeyValues(Object obj, ClassDescriptor mif)
throws PersistenceBrokerException
{
FieldDescriptor[] fks = getForeignKeyFieldDescriptors(mif);
// materialize object only if FK fields are declared
if(fks.length > 0) obj = ProxyHelper.getRealObject(obj);
Object[] result = new Object[fks.length];
for (int i = 0; i < result.length; i++)
{
FieldDescriptor fmd = fks[i];
PersistentField f = fmd.getPersistentField();
// BRJ: do NOT convert.
// conversion is done when binding the sql-statement
//
// FieldConversion fc = fmd.getFieldConversion();
// Object val = fc.javaToSql(f.get(obj));
result[i] = f.get(obj);
}
return result;
}
/**
*
*/
public Class getItemClass()
{
return m_ClassOfItems;
}
/**
* @return the fully qualified name of the item class for this descriptor.
*/
public String getItemClassName()
{
return this.m_ClassOfItems != null ? this.m_ClassOfItems.getName() : null;
}
/**
* sets the item class
* @param c the items class object
*/
public void setItemClass(Class c)
{
m_ClassOfItems = c;
}
/**
*
*/
public Vector getForeignKeyFields()
{
return m_ForeignKeyFields;
}
/**
*
*/
public void setForeignKeyFields(Vector vec)
{
m_ForeignKeyFields = vec;
}
/**
* add a foreign key field ID
*/
public void addForeignKeyField(int newId)
{
if (m_ForeignKeyFields == null)
{
m_ForeignKeyFields = new Vector();
}
m_ForeignKeyFields.add(new Integer(newId));
}
/**
* add a foreign key field
*/
public void addForeignKeyField(String newField)
{
if (m_ForeignKeyFields == null)
{
m_ForeignKeyFields = new Vector();
}
m_ForeignKeyFields.add(newField);
}
/**
* Gets the refresh.
* @return Returns a boolean
*/
public boolean isRefresh()
{
return refresh;
}
/**
* Sets the refresh.
* @param refresh The refresh to set
*/
public void setRefresh(boolean refresh)
{
this.refresh = refresh;
}
/**
* Gets the lazy.
* @return Returns a boolean
*/
public boolean isLazy()
{
return lazy;
}
/**
* Sets the lazy.
* @param lazy The lazy to set
*/
public void setLazy(boolean lazy)
{
this.lazy = lazy;
}
/**
*
*/
public boolean getCascadeRetrieve()
{
return m_CascadeRetrieve;
}
/**
*
*/
public void setCascadeRetrieve(boolean b)
{
m_CascadeRetrieve = b;
}
/**
*
*/
public int getCascadingStore()
{
return m_CascadeStore;
}
/**
*
*/
public void setCascadingStore(int cascade)
{
m_CascadeStore = cascade;
}
public void setCascadingStore(String value)
{
setCascadingStore(getCascadeStoreValue(value));
}
/**
* @deprecated use {@link #getCascadingStore} instead.
*/
public boolean getCascadeStore()
{
return getCascadingStore() == CASCADE_OBJECT;
}
/**
* @deprecated use {@link #setCascadingStore(int)} instead.
*/
public void setCascadeStore(boolean cascade)
{
if(cascade)
{
setCascadingStore(getCascadeStoreValue("true"));
}
else
{
setCascadingStore(getCascadeStoreValue("false"));
}
}
/**
*
*/
public int getCascadingDelete()
{
return m_CascadeDelete;
}
/**
*
*/
public void setCascadingDelete(int cascade)
{
m_CascadeDelete = cascade;
}
public void setCascadingDelete(String value)
{
setCascadingDelete(getCascadeDeleteValue(value));
}
/**
* @deprecated use {@link #getCascadingDelete} instead.
*/
public boolean getCascadeDelete()
{
return getCascadingDelete() == CASCADE_OBJECT;
}
/**
* @deprecated use {@link #setCascadingDelete(int)}
*/
public void setCascadeDelete(boolean cascade)
{
if(cascade)
{
setCascadingDelete(getCascadeDeleteValue("true"));
}
else
{
setCascadingDelete(getCascadeDeleteValue("false"));
}
}
protected int getCascadeStoreValue(String cascade)
{
if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_NONE_STR))
{
return CASCADE_NONE;
}
else if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_LINK_STR))
{
return CASCADE_LINK;
}
else if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_OBJECT_STR))
{
return CASCADE_OBJECT;
}
else if(cascade.equalsIgnoreCase("true"))
{
return CASCADE_OBJECT;
}
else if(cascade.equalsIgnoreCase("false"))
{
/*
in old implementation the FK values of an 1:1 relation are always
set. Thus we choose 'link' instead of 'none'
The CollectionDescriptor should override this behaviour.
*/
return CASCADE_LINK;
}
else
{
throw new OJBRuntimeException("Invalid value! Given value was '" + cascade
+ "', expected values are: " + RepositoryTags.CASCADE_NONE_STR + ", "
+ RepositoryTags.CASCADE_LINK_STR + ", " + RepositoryTags.CASCADE_OBJECT_STR
+ " ('false' and 'true' are deprecated but still valid)");
}
}
protected int getCascadeDeleteValue(String cascade)
{
if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_NONE_STR))
{
return CASCADE_NONE;
}
else if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_LINK_STR))
{
return CASCADE_LINK;
}
else if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_OBJECT_STR))
{
return CASCADE_OBJECT;
}
else if(cascade.equalsIgnoreCase("true"))
{
return CASCADE_OBJECT;
}
else if(cascade.equalsIgnoreCase("false"))
{
return CASCADE_NONE;
}
else
{
throw new OJBRuntimeException("Invalid value! Given value was '" + cascade
+ "', expected values are: " + RepositoryTags.CASCADE_NONE_STR + ", "
+ RepositoryTags.CASCADE_LINK_STR + ", " + RepositoryTags.CASCADE_OBJECT_STR
+ " ('false' and 'true' are deprecated but still valid)");
}
}
public String getCascadeAsString(int cascade)
{
String result = null;
switch(cascade)
{
case CASCADE_NONE:
result = RepositoryTags.CASCADE_NONE_STR;
break;
case CASCADE_LINK:
result = RepositoryTags.CASCADE_LINK_STR;
break;
case CASCADE_OBJECT:
result = RepositoryTags.CASCADE_OBJECT_STR;
break;
}
return result;
}
public int getProxyPrefetchingLimit()
{
return m_ProxyPrefetchingLimit;
}
public void setProxyPrefetchingLimit(int proxyPrefetchingLimit)
{
m_ProxyPrefetchingLimit = proxyPrefetchingLimit;
}
/**
*
*/
public boolean getOtmDependent()
{
return m_OtmDependent;
}
/**
*
*/
public void setOtmDependent(boolean b)
{
m_OtmDependent = b;
}
/**
* Returns <code>true</code> if this descriptor was used to
* describe a reference to a super class of an object.
*
* @return always <code>false</code> for this instance.
*/
public boolean isSuperReferenceDescriptor()
{
return false;
}
/**
* Returns <em>true</em> if a foreign key constraint to the referenced object is
* declared, else <em>false</em> is returned.
*/
public boolean hasConstraint()
{
/*
arminw: Currently we don't have a ForeignKey descriptor object and
a official xml-element to support FK settings. As a workaround I introduce
a custom-attribute to handle FK settings in collection-/reference-decriptor
*/
String result = getAttribute("constraint");
return result != null && result.equalsIgnoreCase("true");
}
/**
* Set a foreign key constraint flag for this reference - see {@link #hasConstraint()}
* @param constraint If set <em>true</em>, signals a foreign key constraint in database.
*/
public void setConstraint(boolean constraint)
{
addAttribute("constraint", constraint ? "true" : "false");
}
public String toString()
{
return new ToStringBuilder(this)
.append("cascade_retrieve", getCascadeRetrieve())
.append("cascade_store", getCascadeAsString(m_CascadeStore))
.append("cascade_delete", getCascadeAsString(m_CascadeDelete))
.append("is_lazy", lazy)
.append("class_of_Items", m_ClassOfItems)
.toString();
}
/*
* @see XmlCapable#toXML()
*/
public String toXML()
{
RepositoryTags tags = RepositoryTags.getInstance();
String eol = System.getProperty( "line.separator" );
// opening tag
StringBuffer result = new StringBuffer( 1024 );
result.append( " " );
result.append( tags.getOpeningTagNonClosingById( REFERENCE_DESCRIPTOR ) );
result.append( eol );
// attributes
// name
String name = this.getAttributeName();
if( name == null )
{
name = RepositoryElements.TAG_SUPER;
}
result.append( " " );
result.append( tags.getAttribute( FIELD_NAME, name ) );
result.append( eol );
// class-ref
result.append( " " );
result.append( tags.getAttribute( REFERENCED_CLASS, this.getItemClassName() ) );
result.append( eol );
// proxyReference is optional
if( isLazy() )
{
result.append( " " );
result.append( tags.getAttribute( PROXY_REFERENCE, "true" ) );
result.append( eol );
result.append( " " );
result.append( tags.getAttribute( PROXY_PREFETCHING_LIMIT, "" + this.getProxyPrefetchingLimit() ) );
result.append( eol );
}
//reference refresh is optional, disabled by default
if( isRefresh() )
{
result.append( " " );
result.append( tags.getAttribute( REFRESH, "true" ) );
result.append( eol );
}
//auto retrieve
result.append( " " );
result.append( tags.getAttribute( AUTO_RETRIEVE, "" + getCascadeRetrieve() ) );
result.append( eol );
//auto update
result.append( " " );
result.append( tags.getAttribute( AUTO_UPDATE, getCascadeAsString( getCascadingStore() ) ) );
result.append( eol );
//auto delete
result.append( " " );
result.append( tags.getAttribute( AUTO_DELETE, getCascadeAsString( getCascadingDelete() ) ) );
result.append( eol );
//otm-dependent is optional, disabled by default
if( getOtmDependent() )
{
result.append( " " );
result.append( tags.getAttribute( OTM_DEPENDENT, "true" ) );
result.append( eol );
}
// close opening tag
result.append( " >" );
result.append( eol );
// elements
// write foreignkey elements
for( int i = 0; i < getForeignKeyFields().size(); i++ )
{
Object obj = getForeignKeyFields().get( i );
if( obj instanceof Integer )
{
String fkId = obj.toString();
result.append( " " );
result.append( tags.getOpeningTagNonClosingById( FOREIGN_KEY ) );
result.append( " " );
result.append( tags.getAttribute( FIELD_ID_REF, fkId ) );
result.append( "/>" );
result.append( eol );
}
else
{
String fk = ( String ) obj;
result.append( " " );
result.append( tags.getOpeningTagNonClosingById( FOREIGN_KEY ) );
result.append( " " );
result.append( tags.getAttribute( FIELD_REF, fk ) );
result.append( "/>" );
result.append( eol );
}
}
// closing tag
result.append( " " );
result.append( tags.getClosingTagById( REFERENCE_DESCRIPTOR ) );
result.append( eol );
return result.toString();
}
}