EOF-Using EOF-Custom EOAdaptor

Last modified by Pascal Robert on 2012/09/22 15:33

It's surprisingly easy to create a simple custom EOAdaptor. You can look at JavaFSAdaptor, which is part of Project Wonder, and you can also look at JavaMemoryAdaptor, also in Wonder.

Here is a little example on how the create an EOAdaptor:

Creating an EOAdaptor can be very straightforward (or very complicated)
 depending on the underlying data source. In this example, and for the
 sake of keeping thing simple, I'm using the file system as a data source
 (well, at least, how java.io.File sees it).
 At a bare minimum you need to implement three classes: EOAdaptor,
 EOAdaptorContext, EOAdaptorChannel.

[EOAdaptor JavaDoc]

Not much in here. The EOAdaptor is mostly a rendezvous point to access
 other functionality in your adaptor. You need to implement a couple of
 abstract methods though:

  • assertConnectionDictionaryIsValid() to check the validity of a
     connection dictionary.
  • createAdaptorContext() to create your own EOAdaptorContext instance.
  • defaultExpressionClass() to return a relevant EOSQLExpression class.
  • isValidQualifierType() to decide if a qualifier can be used in a SQL
     where clause.
  • synchronizationFactory() to return a EOSchemaGeneration helper.

In the case of a file system there is not much to do:


public void assertConnectionDictionaryIsValid()
{
}

public EOAdaptorContext createAdaptorContext()
{
return new FSAdaptorContext( this );
}

public Class defaultExpressionClass()
{
throw new UnsupportedOperationException
( "FSAdaptor.defaultExpressionClass" );
}

public EOSQLExpressionFactory expressionFactory()
{
throw new UnsupportedOperationException
( "FSAdaptor.expressionFactory" );
}

public boolean isValidQualifierType(String aTypeName, EOModel aModel)
{
return true;
}

public EOSchemaGeneration synchronizationFactory()
{
throw new UnsupportedOperationException
( "FSAdaptor.synchronizationFactory" );
}

The only important method is createAdaptorContext().

[EOAdaptorContext JavaDoc]

The adaptor context handles "transactions". It's usually pretty simple
 if your data store support that concept. Not much to do here for a file
 system:


public void beginTransaction()
{
if ( _hasTransaction == false )
{
_hasTransaction = true;

this.transactionDidBegin();
}
}

public void commitTransaction()
{
if ( _hasTransaction == true )
{
_hasTransaction = false;

this.transactionDidCommit();
}
}

public EOAdaptorChannel createAdaptorChannel()
{
return new FSAdaptorChannel( this );
}

public void handleDroppedConnection()
{
}

public void rollbackTransaction()
{
throw new UnsupportedOperationException
( "FSAdaptorContext.rollbackTransaction" );
}

Again createAdaptorChannel() is the only method that you really care
 about in this case.

[EOAdaptorChannel JavaDoc]

That's the real thing. It's where most of the work is done. It's
 important to get this one right. Depending on your back end, it's simply
 an exercise in translating EOQualifiers into something that your data
 storage will understand. Here a the most important methods:

  • deleteRowsDescribedByQualifier() to handle deletion of "row" or
     something equivalent.
  • insertRow() to create a "row".
  • updateValuesInRowsDescribedByQualifier() to update a "row".

Finally, fetching "rows" is a two steps process: first you need to setup
 the "context" of what you are going to fetch and then fetch it:

  • selectAttributes() is where you set your fetch "context".
  • fetchRow is the actual fetching one row at the time.

In the case of a file system here is what you could have:

  • deleteRowsDescribedByQualifier will delete some files (aka "rows"
     based) on a qualifier. 

public int deleteRowsDescribedByQualifier(EOQualifier aQualifier,
EOEntity anEntity)
{
if ( aQualifier != null )
{
if ( anEntity != null )
{
NSArray someFiles =
FSQualifierHandler.filesWithQualifier( aQualifier );

if ( someFiles != null )
{
someFiles = this.filteredArrayWithEntity( someFiles, anEntity );

if ( someFiles != null )
{
int count = someFiles.count();
int counter = 0;

for ( int index = 0; index < count; index++ )
{
File aFile = (File) someFiles.objectAtIndex( index );

if ( aFile.delete() == true )
{
counter += 1;
}
}

return counter;
}
}

return 0;
}

throw new IllegalArgumentException
( "FSAdaptorChannel.deleteRowsDescribedByQualifier: null entity." );
}

throw new IllegalArgumentException
( "FSAdaptorChannel.deleteRowsDescribedByQualifier: null qualifier." );
}
  • insertRow will create a file or directory. 

public void insertRow(NSDictionary aRow, EOEntity anEntity)
{
if ( aRow != null )
{
if ( anEntity != null )
{
String aPath = (String) aRow.objectForKey( "absolutePath" );

if ( aPath != null )
{
File aFile = new File( aPath );

try
{
if ( anEntity.name().equals( "FSDirectory" ) == true )
{
aFile.mkdirs();
}
else
{
aFile.createNewFile();
}
}
catch(Exception anException)
{
throw new RuntimeException
( "FSAdaptorChannel.insertRow: " + anException );
}

return;
}

throw new IllegalArgumentException
( "FSAdaptorChannel.insertRow: null absolutePath." );
}

throw new IllegalArgumentException
( "FSAdaptorChannel.insertRow: null entity." );
}

throw new IllegalArgumentException
( "FSAdaptorChannel.insertRow: null row." );
}
  • updateValuesInRowsDescribedByQualifier will change a file attributes. 

public int updateValuesInRowsDescribedByQualifier(NSDictionary
aRow, EOQualifier aQualifier, EOEntity anEntity)
{
if ( aRow != null )
{
if ( aQualifier != null )
{
if ( anEntity != null )
{
NSArray someFiles =
FSQualifierHandler.filesWithQualifier( aQualifier );

if ( someFiles != null )
{
someFiles = this.filteredArrayWithEntity( someFiles, anEntity );

if ( someFiles != null )
{
int count = someFiles.count();

for ( int index = 0; index < count; index++ )
{
File aFile = (File) someFiles.objectAtIndex( index );
NSArray someKeys = aRow.allKeys();
int keyCount = someKeys.count();

for ( int keyIndex = 0; keyIndex < count; keyIndex++ )
{
Object aKey = someKeys.objectAtIndex( keyIndex );
EOAttribute anAttribute =
anEntity.attributeNamed( aKey.toString() );

if ( anAttribute != null )
{
Object aValue = aRow.objectForKey( aKey );


NSKeyValueCoding.DefaultImplementation.takeValueForKey( aFile, aValue,
anAttribute.columnName() );
}
}

}

return count;
}
}

return 0;
}

throw new IllegalArgumentException
( "FSAdaptorChannel.updateValuesInRowsDescribedByQualifier: null
entity." );
}

throw new IllegalArgumentException
( "FSAdaptorChannel.updateValuesInRowsDescribedByQualifier: null
qualifier." );
}

throw new IllegalArgumentException
( "FSAdaptorChannel.updateValuesInRowsDescribedByQualifier: null row." );
}
  • selectAttributes will retrieve some files based on a qualifier. 

public void selectAttributes(NSArray someAttributes,
EOFetchSpecification aFetchSpecification, boolean shouldLock, EOEntity
anEntity)
{
if ( someAttributes != null )
{
this.setAttributesToFetch( someAttributes );

if ( aFetchSpecification != null )
{
if ( anEntity != null )
{
NSArray someFiles =
FSQualifierHandler.filesWithQualifier( aFetchSpecification.qualifier() );

if ( someFiles != null )
{
NSArray someSortOrderings = aFetchSpecification.sortOrderings();

if ( someSortOrderings != null )
{
someFiles =
EOSortOrdering.sortedArrayUsingKeyOrderArray( someFiles,
someSortOrderings );
}

someFiles = this.filteredArrayWithEntity( someFiles, anEntity );

if ( someFiles != null )
{
this.files().addObjectsFromArray( someFiles );
}
}

return;
}

throw new IllegalArgumentException
( "FSAdaptorChannel.selectAttributes: null entity." );
}

throw new IllegalArgumentException
( "FSAdaptorChannel.selectAttributes: null fetch specification." );
}

throw new IllegalArgumentException
( "FSAdaptorChannel.selectAttributes: null attributes." );
}
  • fetchRow will "fetch" a dictionary representation of a file. 

public NSMutableDictionary fetchRow()
{
File aFile = (File) this.files().lastObject();

if ( aFile != null )
{
this.files().removeLastObject();

return this.dictionaryForFileWithAttributes( aFile,
this.attributesToFetch() );
}

return null;
}

That could be it. Nothing to involved. However there is two extra
 methods that make sense to implement: describeTableNames and
 describeModelWithTableNames.

  • describeTableNames() return an array of "table" (or something
     conceptually equivalent) existing in you data back end.
  • describeModelWithTableNames() will create a default model for some of
     these "tables".

This is very useful to get you started in EOModeler instead of having to
 create the entire model by hand. In the case of a file system, there is
 only one model:


public EOModel describeModelWithTableNames(NSArray anArray)
{
return new EOModel( this.defaultModelPath() );
}

And voilà. No you have a toy file system adaptor (aka JavaFSAdaptor).

  • To "fetch" some files, simply create a qualifier with some path
     attributes (eg absolutePath): 

EOQualifier aPathQualifier = new EOKeyValueQualifier
( "absolutePath", EOQualifier.QualifierOperatorEqual, System.getProperty
( "user.home" ) );
  • To get all the files in a directory, use the "parent" attribute: 

EOQualifier aPathQualifier = new EOKeyValueQualifier( "parent",
EOQualifier.QualifierOperatorEqual, System.getProperty( "user.home" ) );

Once you have your qualifier, simply build a fetch specification for it
 and get your "file" EOs:


EOFetchSpecification aFetchSpecification = new EOFetchSpecification
( "FSDirectory", aQualifier, null );
NSArray someObjects =
anEditingContext.objectsWithFetchSpecification( aFetchSpecification );

You can use FSFile to retrieve files only. FSDirectory for, er,
 directories. And FSItem for both. FSDirectory have two handy
 relationships: "files" and "directories". Check the attached model.

  • To "create" a file (or directory), you can do something along those
     lines: 

EOClassDescription aClassDescription =
EOClassDescription.classDescriptionForEntityName( "FSDirectory" );
EOEnterpriseObject anObject =
aClassDescription.createInstanceWithEditingContext( anEditingContext,
null );

anObject.takeValueForKey
( "/Users/rszwarc/tmp/JavaFSAdaptor/TestFSDirectory", "absolutePath" );

anEditingContext.insertObject( anObject );

anEditingContext.saveChanges();
  • Finally, do delete a file: 

anEditingContext.deleteObject( anObject );

anEditingContext.saveChanges();