I have an existing database which my app has been coded with a DatabaseContextDelegate to handle primary key generation.
I model all Entities such that primary keys and foreign keys are NOT generated (getters or setters) in the class files.
public class DatabaseContextDelegate1 extends Object {
protected static Object utilValueForKey( Object object, String key )
{
try {
return NSKeyValueCoding.Utility.valueForKey( object, key );
}
catch ( NSKeyValueCoding.UnknownKeyException e )
{
NSLog.err.appendln( "DatabaseContextDelegate1.utilValueForKey: e = " + e );
e.printStackTrace();
NSLog.err.appendln( "returning null ..." );
return null;
}
}
// --- EODatabaseContext.Delegate methods --
public NSDictionary databaseContextNewPrimaryKey( EODatabaseContext databaseContext,
Object object,
EOEntity entity )
{
String debugPrefix = "DatabaseContextDelegate1.databaseContextNewPrimaryKey: ";
// get primary key attributes (description of them) from entity (description of) requesting new primary key(s)
NSArray primaryKeyAttributes = entity.primaryKeyAttributes();
// if *NOT* EXACTLY ONE primary key attribute,
// return primary key dictionary for object and primary key attributes
// NOTE: may return null if object does not have values for ALL primary key attributes, thus
// allowing someone else (superclass) to handle primary keys
if ( primaryKeyAttributes.count() != 1 )
{
NSDictionary primaryKeyDictionary =
primaryKeyDictionaryForObjectAndPrimaryKeyAttributes( object, primaryKeyAttributes );
return primaryKeyDictionary;
}
// NOTE: at this point primary key attributes contain EXACTLY ONE primary key attribute
// get description of first (and only) primary key attribute
EOAttribute primaryKeyAttribute = (EOAttribute)primaryKeyAttributes.objectAtIndex(0);
if ( primaryKeyAttribute.adaptorValueType() != EOAttribute.AdaptorBytesType )
{
// We support only number keys, so call the superclass
return null;
}
// setup variables for further processing
EOAdaptorChannel channel = null;
NSDictionary primaryKeyDictionary = null;
try
{
databaseContext.lock();
channel = databaseContext.availableChannel().adaptorChannel();
if ( ! channel.isOpen() )
channel.openChannel();
NSDictionary row = null;
try
{
EOSQLExpressionFactory factory =
new EOSQLExpressionFactory( databaseContext.adaptorContext().adaptor() );
EOSQLExpression getRowExpr = factory.expressionForString( "SELECT top 1 VALUE FROM vNewUUID" );
channel.evaluateExpression( getRowExpr );
row = channel.fetchRow();
channel.cancelFetch();
}
catch ( Throwable localException ) {
channel.cancelFetch();
}
if ( row != null )
{
NSData newUUID = (NSData)row.objectForKey( "VALUE" );
if ( newUUID != null )
primaryKeyDictionary = new NSDictionary( (Object)newUUID, (Object)primaryKeyAttribute.name() );
else
NSLog.err.appendln( debugPrefix + "got NULL newUUID for object " + object );
}
else NSLog.err.appendln( debugPrefix + "could not set keys for object " + object );
}
catch ( Throwable ex ) {
System.err.println( ex.toString() );
}
finally
{
if ( channel.isOpen() && channel.isFetchInProgress() )
channel.cancelFetch();
databaseContext.unlock();
}
// NOTE: may return null allowing someone else (superclass) to handle primary keys
return primaryKeyDictionary;
}
// --- utility ---
public NSDictionary primaryKeyDictionaryForObjectAndPrimaryKeyAttributes( Object object,
NSArray primaryKeyAttributes )
{
String debugPrefix = "DatabaseContextDelegate1.primaryKeyDictionaryForObjectAndPrimaryKeyAttributes: ";
int primaryKeyAttributeCount = primaryKeyAttributes.count();
// initialize "got all keys" to TRUE (to be set to FALSE if one is found missing)
boolean gotAllKeys = true;
// initialize empty primary key dictionary
NSMutableDictionary primaryKeyDictionary = new NSMutableDictionary();
// loop thru primary key attributes
for ( int x = 0 ; x < primaryKeyAttributeCount ; x++ )
{
// get next (primary key) attribute
EOAttribute attribute = (EOAttribute)primaryKeyAttributes.objectAtIndex(x);
// get attribute name
String attributeName = attribute.name();
// get value from object for attribute by attribute name
Object valueFromEO = utilValueForKey( object, attributeName );
// if the object has a value, add it to the primary key dictionary
// otherwise, flag "got all keys" as FALSE
if ( valueFromEO != null )
primaryKeyDictionary.setObjectForKey( valueFromEO, attributeName );
else
{
System.out.println( debugPrefix + "attributeName = " + attributeName + " ... no value from EO ... setting gotAllKeys to FALSE" );
gotAllKeys = false;
}
}
// if we got values for ALL keys from the object,
// return the primary key dictionary
if ( gotAllKeys )
{
NSLog.debug.appendln( debugPrefix + "returning primary key " + primaryKeyDictionary + " from object " + object );
return primaryKeyDictionary;
}
// NOTE: at this point we did NOT get values for ALL keys from the object ...
// We support only simple primary keys, data keys are handled by superclass
// thus DO NOT return the primary key dictionary, instead return NULL (nothing)
NSLog.err.appendln( debugPrefix + "could not set keys for object " + object );
return null;
}
}
It's all straight forward and works for single primary keys. Where it has problems is when a compound primary key element is a foreign key (a relationship). I think I need the primaryKeyDictionaryForObjectAndPrimaryKeyAttributes function to correctly build the primary key dictionary, but I don't know how to do it, especially with the primary key components being foreign keys (used in relationships that are set, of course). I simplified the test case:
Company:
pkID (binary)
*name (String)
Product:
cpkFkCompanyID (binary)
*cpkCode (String)
*name (String)
*only these items have diamonds on them in EOModeler
Company <--->> Product
default Product. cpkCode value is "xxx"
EODatabaseContext.setDefaultDelegate( new DatabaseContextDelegate1() );
EOEditingContext ec = new EOEditingContext();
EOEnterpriseObject company = EOUtilities.createAndInsertInstance( ec, "Company" );
EOEnterpriseObject product = EOUtilities.createAndInsertInstance( ec, "Product" );
product.addObjectToBothSidesOfRelationshipWithKey( company, "company" );
product.takeValueForKey( "xxx", "cpkCode" );
ec.saveChanges();
Sounds simple enough right?
Here's my error:
DatabaseContextDelegate1.primaryKeyDictionaryForObjectAndPrimaryKeyAttributes: attributeName = cpkFkCompanyID ... no value from EO ... setting gotAllKeys to FALSE
[2008-06-10 11:18:33 CDT] <main> DatabaseContextDelegate1.primaryKeyDictionaryForObjectAndPrimaryKeyAttributes: could not set keys for object {values = {Company = "<com.webobjects.eocontrol.EOGenericRecord e2350a <EOTemporaryGlobalID: 0 0 -64 -88 2 1 0 0 -40 42 1 0 0 0 1 26 115 69 -122 118 -65 -101 100 1>>"; cpkCode = "xxx"; }; this = "<com.webobjects.eocontrol.EOGenericRecord abcd5e <EOTemporaryGlobalID: 0 0 -64 -88 2 1 0 0 -40 42 2 0 0 0 1 26 115 69 -122 118 -65 -101 100 1>>"; }
<-- (2) DatabaseContextDelegate1.primaryKeyDictionaryForObjectAndPrimaryKeyAttributes:
<-- (1) DatabaseContextDelegate1.databaseContextNewPrimaryKey:
[2008-06-10 11:18:33 CDT] <main> A fatal exception occurred: Adaptor com.webobjects.jdbcadaptor.JDBCAdaptor@609812 failed to provide new primary keys for entity 'Product'
[2008-06-10 11:18:33 CDT] <main> java.lang.IllegalStateException: Adaptor com.webobjects.jdbcadaptor.JDBCAdaptor@609812 failed to provide new primary keys for entity 'Product'
at com.webobjects.eoaccess.EODatabaseContext.prepareForSaveWithCoordinator(EODatabaseContext.java:5885)
at com.webobjects.eocontrol.EOObjectStoreCoordinator.saveChangesInEditingContext(EOObjectStoreCoordinator.java:409)
at com.webobjects.eocontrol.EOEditingContext.saveChanges(EOEditingContext.java:3226)
at Application.<init>(Application.java:31)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:494)
at java.lang.Class.newInstance0(Class.java:350)
at java.lang.Class.newInstance(Class.java:303)
at com.webobjects.appserver.WOApplication.main(WOApplication.java:323)
at Application.main(Application.java:16)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at com.webobjects._bootstrap.WOBootstrap.main(WOBootstrap.java:71)
If anyone has ANY suggestions, please email me as soon as possible!
Thanks!
= Robert =