Wiki source code of EOF-Using EOF-Custom EOAdaptor
Last modified by Pascal Robert on 2012/09/22 15:33
Hide last authors
| author | version | line-number | content |
|---|---|---|---|
| |
8.1 | 1 | 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. |
| |
1.1 | 2 | |
| |
6.1 | 3 | Here is a little example on how the create an EOAdaptor: |
| 4 | |||
| |
7.1 | 5 | Creating an EOAdaptor can be very straightforward (or very complicated) |
| |
8.1 | 6 | depending on the underlying data source. In this example, and for the |
| 7 | sake of keeping thing simple, I'm using the file system as a data source | ||
| 8 | (well, at least, how java.io.File sees it). | ||
| 9 | At a bare minimum you need to implement three classes: EOAdaptor, | ||
| 10 | EOAdaptorContext, EOAdaptorChannel. | ||
| |
6.1 | 11 | |
| |
8.1 | 12 | ~[[[EOAdaptor JavaDoc>>url:http://wocommunity.org/documents/javadoc/WebObjects/5.4.2/com/webobjects/eoaccess/EOAdaptor.html?is-external=true||shape="rect"]]] |
| |
6.1 | 13 | |
| |
7.1 | 14 | Not much in here. The EOAdaptor is mostly a rendezvous point to access |
| |
8.1 | 15 | other functionality in your adaptor. You need to implement a couple of |
| 16 | abstract methods though: | ||
| |
6.1 | 17 | |
| |
8.1 | 18 | (% class="alternate" %) |
| |
7.1 | 19 | * assertConnectionDictionaryIsValid() to check the validity of a |
| |
8.1 | 20 | connection dictionary. |
| |
6.1 | 21 | * createAdaptorContext() to create your own EOAdaptorContext instance. |
| 22 | * defaultExpressionClass() to return a relevant EOSQLExpression class. | ||
| |
7.1 | 23 | * isValidQualifierType() to decide if a qualifier can be used in a SQL |
| |
8.1 | 24 | where clause. |
| |
6.1 | 25 | * synchronizationFactory() to return a EOSchemaGeneration helper. |
| 26 | |||
| 27 | In the case of a file system there is not much to do: | ||
| 28 | |||
| 29 | {{code}} | ||
| 30 | |||
| |
7.1 | 31 | public void assertConnectionDictionaryIsValid() |
| |
6.1 | 32 | { |
| 33 | } | ||
| |
7.1 | 34 | |
| |
6.1 | 35 | public EOAdaptorContext createAdaptorContext() |
| 36 | { | ||
| 37 | return new FSAdaptorContext( this ); | ||
| 38 | } | ||
| |
7.1 | 39 | |
| |
6.1 | 40 | public Class defaultExpressionClass() |
| 41 | { | ||
| 42 | throw new UnsupportedOperationException | ||
| 43 | ( "FSAdaptor.defaultExpressionClass" ); | ||
| 44 | } | ||
| |
7.1 | 45 | |
| |
6.1 | 46 | public EOSQLExpressionFactory expressionFactory() |
| 47 | { | ||
| 48 | throw new UnsupportedOperationException | ||
| 49 | ( "FSAdaptor.expressionFactory" ); | ||
| 50 | } | ||
| |
7.1 | 51 | |
| |
6.1 | 52 | public boolean isValidQualifierType(String aTypeName, EOModel aModel) |
| 53 | { | ||
| 54 | return true; | ||
| 55 | } | ||
| |
7.1 | 56 | |
| |
6.1 | 57 | public EOSchemaGeneration synchronizationFactory() |
| 58 | { | ||
| 59 | throw new UnsupportedOperationException | ||
| 60 | ( "FSAdaptor.synchronizationFactory" ); | ||
| 61 | } | ||
| 62 | |||
| 63 | {{/code}} | ||
| 64 | |||
| 65 | The only important method is createAdaptorContext(). | ||
| 66 | |||
| |
8.1 | 67 | ~[[[EOAdaptorContext JavaDoc>>url:http://wocommunity.org/documents/javadoc/WebObjects/5.4.2/com/webobjects/eoaccess/EOAdaptorContext.html?is-external=true||shape="rect"]]] |
| |
6.1 | 68 | |
| |
7.1 | 69 | The adaptor context handles "transactions". It's usually pretty simple |
| |
8.1 | 70 | if your data store support that concept. Not much to do here for a file |
| 71 | system: | ||
| |
6.1 | 72 | |
| 73 | {{code}} | ||
| 74 | |||
| |
7.1 | 75 | public void beginTransaction() |
| |
6.1 | 76 | { |
| 77 | if ( _hasTransaction == false ) | ||
| 78 | { | ||
| 79 | _hasTransaction = true; | ||
| 80 | |||
| 81 | this.transactionDidBegin(); | ||
| 82 | } | ||
| 83 | } | ||
| |
7.1 | 84 | |
| |
6.1 | 85 | public void commitTransaction() |
| 86 | { | ||
| 87 | if ( _hasTransaction == true ) | ||
| 88 | { | ||
| 89 | _hasTransaction = false; | ||
| |
7.1 | 90 | |
| |
6.1 | 91 | this.transactionDidCommit(); |
| 92 | } | ||
| 93 | } | ||
| |
7.1 | 94 | |
| |
6.1 | 95 | public EOAdaptorChannel createAdaptorChannel() |
| 96 | { | ||
| 97 | return new FSAdaptorChannel( this ); | ||
| 98 | } | ||
| |
7.1 | 99 | |
| |
6.1 | 100 | public void handleDroppedConnection() |
| 101 | { | ||
| 102 | } | ||
| 103 | |||
| 104 | public void rollbackTransaction() | ||
| 105 | { | ||
| 106 | throw new UnsupportedOperationException | ||
| 107 | ( "FSAdaptorContext.rollbackTransaction" ); | ||
| 108 | } | ||
| 109 | |||
| 110 | {{/code}} | ||
| 111 | |||
| |
7.1 | 112 | Again createAdaptorChannel() is the only method that you really care |
| |
8.1 | 113 | about in this case. |
| |
6.1 | 114 | |
| |
8.1 | 115 | ~[[[EOAdaptorChannel JavaDoc>>url:http://wocommunity.org/documents/javadoc/WebObjects/5.4.2/com/webobjects/eoaccess/EOAdaptorChannel.html?is-external=true||shape="rect"]]] |
| |
6.1 | 116 | |
| |
7.1 | 117 | That's the real thing. It's where most of the work is done. It's |
| |
8.1 | 118 | important to get this one right. Depending on your back end, it's simply |
| 119 | an exercise in translating EOQualifiers into something that your data | ||
| 120 | storage will understand. Here a the most important methods: | ||
| |
6.1 | 121 | |
| |
8.1 | 122 | (% class="alternate" %) |
| |
7.1 | 123 | * deleteRowsDescribedByQualifier() to handle deletion of "row" or |
| |
8.1 | 124 | something equivalent. |
| |
6.1 | 125 | * insertRow() to create a "row". |
| 126 | * updateValuesInRowsDescribedByQualifier() to update a "row". | ||
| 127 | |||
| |
7.1 | 128 | Finally, fetching "rows" is a two steps process: first you need to setup |
| |
8.1 | 129 | the "context" of what you are going to fetch and then fetch it: |
| |
6.1 | 130 | |
| |
8.1 | 131 | (% class="alternate" %) |
| |
6.1 | 132 | * selectAttributes() is where you set your fetch "context". |
| 133 | * fetchRow is the actual fetching one row at the time. | ||
| 134 | |||
| 135 | In the case of a file system here is what you could have: | ||
| 136 | |||
| |
8.1 | 137 | (% class="alternate" %) |
| |
7.1 | 138 | * deleteRowsDescribedByQualifier will delete some files (aka "rows" |
| |
8.1 | 139 | based) on a qualifier. |
| |
6.1 | 140 | |
| 141 | {{code}} | ||
| 142 | |||
| |
7.1 | 143 | public int deleteRowsDescribedByQualifier(EOQualifier aQualifier, |
| |
6.1 | 144 | EOEntity anEntity) |
| 145 | { | ||
| 146 | if ( aQualifier != null ) | ||
| 147 | { | ||
| 148 | if ( anEntity != null ) | ||
| 149 | { | ||
| |
7.1 | 150 | NSArray someFiles = |
| |
6.1 | 151 | FSQualifierHandler.filesWithQualifier( aQualifier ); |
| |
7.1 | 152 | |
| |
6.1 | 153 | if ( someFiles != null ) |
| 154 | { | ||
| 155 | someFiles = this.filteredArrayWithEntity( someFiles, anEntity ); | ||
| |
7.1 | 156 | |
| |
6.1 | 157 | if ( someFiles != null ) |
| 158 | { | ||
| 159 | int count = someFiles.count(); | ||
| 160 | int counter = 0; | ||
| |
7.1 | 161 | |
| |
6.1 | 162 | for ( int index = 0; index < count; index++ ) |
| 163 | { | ||
| 164 | File aFile = (File) someFiles.objectAtIndex( index ); | ||
| |
7.1 | 165 | |
| |
6.1 | 166 | if ( aFile.delete() == true ) |
| 167 | { | ||
| 168 | counter += 1; | ||
| 169 | } | ||
| 170 | } | ||
| |
7.1 | 171 | |
| |
6.1 | 172 | return counter; |
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | return 0; | ||
| 177 | } | ||
| 178 | |||
| 179 | throw new IllegalArgumentException | ||
| 180 | ( "FSAdaptorChannel.deleteRowsDescribedByQualifier: null entity." ); | ||
| 181 | } | ||
| 182 | |||
| 183 | throw new IllegalArgumentException | ||
| 184 | ( "FSAdaptorChannel.deleteRowsDescribedByQualifier: null qualifier." ); | ||
| 185 | } | ||
| 186 | |||
| 187 | {{/code}} | ||
| 188 | |||
| |
8.1 | 189 | (% class="alternate" %) |
| 190 | * insertRow will create a file or directory. | ||
| |
6.1 | 191 | |
| 192 | {{code}} | ||
| 193 | |||
| |
7.1 | 194 | public void insertRow(NSDictionary aRow, EOEntity anEntity) |
| |
6.1 | 195 | { |
| 196 | if ( aRow != null ) | ||
| 197 | { | ||
| 198 | if ( anEntity != null ) | ||
| 199 | { | ||
| 200 | String aPath = (String) aRow.objectForKey( "absolutePath" ); | ||
| |
7.1 | 201 | |
| |
6.1 | 202 | if ( aPath != null ) |
| 203 | { | ||
| 204 | File aFile = new File( aPath ); | ||
| |
7.1 | 205 | |
| |
6.1 | 206 | try |
| 207 | { | ||
| 208 | if ( anEntity.name().equals( "FSDirectory" ) == true ) | ||
| 209 | { | ||
| 210 | aFile.mkdirs(); | ||
| 211 | } | ||
| 212 | else | ||
| 213 | { | ||
| 214 | aFile.createNewFile(); | ||
| 215 | } | ||
| 216 | } | ||
| 217 | catch(Exception anException) | ||
| 218 | { | ||
| 219 | throw new RuntimeException | ||
| 220 | ( "FSAdaptorChannel.insertRow: " + anException ); | ||
| 221 | } | ||
| |
7.1 | 222 | |
| |
6.1 | 223 | return; |
| 224 | } | ||
| 225 | |||
| 226 | throw new IllegalArgumentException | ||
| 227 | ( "FSAdaptorChannel.insertRow: null absolutePath." ); | ||
| 228 | } | ||
| 229 | |||
| 230 | throw new IllegalArgumentException | ||
| 231 | ( "FSAdaptorChannel.insertRow: null entity." ); | ||
| 232 | } | ||
| |
7.1 | 233 | |
| |
6.1 | 234 | throw new IllegalArgumentException |
| 235 | ( "FSAdaptorChannel.insertRow: null row." ); | ||
| 236 | } | ||
| 237 | |||
| 238 | {{/code}} | ||
| 239 | |||
| |
8.1 | 240 | (% class="alternate" %) |
| 241 | * updateValuesInRowsDescribedByQualifier will change a file attributes. | ||
| |
6.1 | 242 | |
| 243 | {{code}} | ||
| 244 | |||
| |
7.1 | 245 | public int updateValuesInRowsDescribedByQualifier(NSDictionary |
| |
6.1 | 246 | aRow, EOQualifier aQualifier, EOEntity anEntity) |
| 247 | { | ||
| 248 | if ( aRow != null ) | ||
| 249 | { | ||
| 250 | if ( aQualifier != null ) | ||
| 251 | { | ||
| 252 | if ( anEntity != null ) | ||
| 253 | { | ||
| |
7.1 | 254 | NSArray someFiles = |
| |
6.1 | 255 | FSQualifierHandler.filesWithQualifier( aQualifier ); |
| |
7.1 | 256 | |
| |
6.1 | 257 | if ( someFiles != null ) |
| 258 | { | ||
| 259 | someFiles = this.filteredArrayWithEntity( someFiles, anEntity ); | ||
| |
7.1 | 260 | |
| |
6.1 | 261 | if ( someFiles != null ) |
| 262 | { | ||
| 263 | int count = someFiles.count(); | ||
| |
7.1 | 264 | |
| |
6.1 | 265 | for ( int index = 0; index < count; index++ ) |
| 266 | { | ||
| 267 | File aFile = (File) someFiles.objectAtIndex( index ); | ||
| 268 | NSArray someKeys = aRow.allKeys(); | ||
| 269 | int keyCount = someKeys.count(); | ||
| |
7.1 | 270 | |
| |
6.1 | 271 | for ( int keyIndex = 0; keyIndex < count; keyIndex++ ) |
| 272 | { | ||
| 273 | Object aKey = someKeys.objectAtIndex( keyIndex ); | ||
| |
7.1 | 274 | EOAttribute anAttribute = |
| |
6.1 | 275 | anEntity.attributeNamed( aKey.toString() ); |
| |
7.1 | 276 | |
| |
6.1 | 277 | if ( anAttribute != null ) |
| 278 | { | ||
| 279 | Object aValue = aRow.objectForKey( aKey ); | ||
| |
7.1 | 280 | |
| 281 | |||
| 282 | NSKeyValueCoding.DefaultImplementation.takeValueForKey( aFile, aValue, | ||
| |
6.1 | 283 | anAttribute.columnName() ); |
| 284 | } | ||
| 285 | } | ||
| |
7.1 | 286 | |
| |
6.1 | 287 | } |
| |
7.1 | 288 | |
| |
6.1 | 289 | return count; |
| 290 | } | ||
| 291 | } | ||
| 292 | |||
| 293 | return 0; | ||
| 294 | } | ||
| 295 | |||
| 296 | throw new IllegalArgumentException | ||
| |
7.1 | 297 | ( "FSAdaptorChannel.updateValuesInRowsDescribedByQualifier: null |
| |
6.1 | 298 | entity." ); |
| 299 | } | ||
| |
7.1 | 300 | |
| |
6.1 | 301 | throw new IllegalArgumentException |
| |
7.1 | 302 | ( "FSAdaptorChannel.updateValuesInRowsDescribedByQualifier: null |
| |
6.1 | 303 | qualifier." ); |
| 304 | } | ||
| |
7.1 | 305 | |
| |
6.1 | 306 | throw new IllegalArgumentException |
| 307 | ( "FSAdaptorChannel.updateValuesInRowsDescribedByQualifier: null row." ); | ||
| 308 | } | ||
| 309 | |||
| 310 | {{/code}} | ||
| 311 | |||
| |
8.1 | 312 | (% class="alternate" %) |
| 313 | * selectAttributes will retrieve some files based on a qualifier. | ||
| |
6.1 | 314 | |
| 315 | {{code}} | ||
| 316 | |||
| |
7.1 | 317 | public void selectAttributes(NSArray someAttributes, |
| 318 | EOFetchSpecification aFetchSpecification, boolean shouldLock, EOEntity | ||
| |
6.1 | 319 | anEntity) |
| 320 | { | ||
| 321 | if ( someAttributes != null ) | ||
| 322 | { | ||
| 323 | this.setAttributesToFetch( someAttributes ); | ||
| |
7.1 | 324 | |
| |
6.1 | 325 | if ( aFetchSpecification != null ) |
| 326 | { | ||
| 327 | if ( anEntity != null ) | ||
| 328 | { | ||
| |
7.1 | 329 | NSArray someFiles = |
| |
6.1 | 330 | FSQualifierHandler.filesWithQualifier( aFetchSpecification.qualifier() ); |
| |
7.1 | 331 | |
| |
6.1 | 332 | if ( someFiles != null ) |
| 333 | { | ||
| 334 | NSArray someSortOrderings = aFetchSpecification.sortOrderings(); | ||
| |
7.1 | 335 | |
| |
6.1 | 336 | if ( someSortOrderings != null ) |
| 337 | { | ||
| |
7.1 | 338 | someFiles = |
| 339 | EOSortOrdering.sortedArrayUsingKeyOrderArray( someFiles, | ||
| |
6.1 | 340 | someSortOrderings ); |
| 341 | } | ||
| |
7.1 | 342 | |
| |
6.1 | 343 | someFiles = this.filteredArrayWithEntity( someFiles, anEntity ); |
| |
7.1 | 344 | |
| |
6.1 | 345 | if ( someFiles != null ) |
| 346 | { | ||
| 347 | this.files().addObjectsFromArray( someFiles ); | ||
| 348 | } | ||
| 349 | } | ||
| |
7.1 | 350 | |
| |
6.1 | 351 | return; |
| 352 | } | ||
| 353 | |||
| 354 | throw new IllegalArgumentException | ||
| 355 | ( "FSAdaptorChannel.selectAttributes: null entity." ); | ||
| 356 | } | ||
| 357 | |||
| 358 | throw new IllegalArgumentException | ||
| 359 | ( "FSAdaptorChannel.selectAttributes: null fetch specification." ); | ||
| 360 | } | ||
| |
7.1 | 361 | |
| |
6.1 | 362 | throw new IllegalArgumentException |
| 363 | ( "FSAdaptorChannel.selectAttributes: null attributes." ); | ||
| 364 | } | ||
| 365 | |||
| 366 | {{/code}} | ||
| 367 | |||
| |
8.1 | 368 | (% class="alternate" %) |
| 369 | * fetchRow will "fetch" a dictionary representation of a file. | ||
| |
6.1 | 370 | |
| 371 | {{code}} | ||
| 372 | |||
| |
7.1 | 373 | public NSMutableDictionary fetchRow() |
| |
6.1 | 374 | { |
| 375 | File aFile = (File) this.files().lastObject(); | ||
| |
7.1 | 376 | |
| |
6.1 | 377 | if ( aFile != null ) |
| 378 | { | ||
| 379 | this.files().removeLastObject(); | ||
| |
7.1 | 380 | |
| 381 | return this.dictionaryForFileWithAttributes( aFile, | ||
| |
6.1 | 382 | this.attributesToFetch() ); |
| 383 | } | ||
| |
7.1 | 384 | |
| |
6.1 | 385 | return null; |
| 386 | } | ||
| 387 | |||
| 388 | {{/code}} | ||
| 389 | |||
| |
7.1 | 390 | That could be it. Nothing to involved. However there is two extra |
| |
8.1 | 391 | methods that make sense to implement: describeTableNames and |
| 392 | describeModelWithTableNames. | ||
| |
6.1 | 393 | |
| |
8.1 | 394 | (% class="alternate" %) |
| |
7.1 | 395 | * describeTableNames() return an array of "table" (or something |
| |
8.1 | 396 | conceptually equivalent) existing in you data back end. |
| |
7.1 | 397 | * describeModelWithTableNames() will create a default model for some of |
| |
8.1 | 398 | these "tables". |
| |
6.1 | 399 | |
| |
7.1 | 400 | This is very useful to get you started in EOModeler instead of having to |
| |
8.1 | 401 | create the entire model by hand. In the case of a file system, there is |
| 402 | only one model: | ||
| |
6.1 | 403 | |
| 404 | {{code}} | ||
| 405 | |||
| |
7.1 | 406 | public EOModel describeModelWithTableNames(NSArray anArray) |
| |
6.1 | 407 | { |
| 408 | return new EOModel( this.defaultModelPath() ); | ||
| 409 | } | ||
| 410 | |||
| 411 | {{/code}} | ||
| 412 | |||
| 413 | And voilĂ . No you have a toy file system adaptor (aka JavaFSAdaptor). | ||
| 414 | |||
| |
8.1 | 415 | (% class="alternate" %) |
| |
7.1 | 416 | * To "fetch" some files, simply create a qualifier with some path |
| |
8.1 | 417 | attributes (eg absolutePath): |
| |
6.1 | 418 | |
| 419 | {{code}} | ||
| 420 | |||
| 421 | EOQualifier aPathQualifier = new EOKeyValueQualifier | ||
| 422 | ( "absolutePath", EOQualifier.QualifierOperatorEqual, System.getProperty | ||
| 423 | ( "user.home" ) ); | ||
| 424 | |||
| 425 | {{/code}} | ||
| 426 | |||
| |
8.1 | 427 | (% class="alternate" %) |
| 428 | * To get all the files in a directory, use the "parent" attribute: | ||
| |
6.1 | 429 | |
| 430 | {{code}} | ||
| 431 | |||
| |
7.1 | 432 | EOQualifier aPathQualifier = new EOKeyValueQualifier( "parent", |
| |
6.1 | 433 | EOQualifier.QualifierOperatorEqual, System.getProperty( "user.home" ) ); |
| 434 | |||
| 435 | {{/code}} | ||
| 436 | |||
| |
7.1 | 437 | Once you have your qualifier, simply build a fetch specification for it |
| |
8.1 | 438 | and get your "file" EOs: |
| |
6.1 | 439 | |
| 440 | {{code}} | ||
| 441 | |||
| 442 | EOFetchSpecification aFetchSpecification = new EOFetchSpecification | ||
| 443 | ( "FSDirectory", aQualifier, null ); | ||
| |
7.1 | 444 | NSArray someObjects = |
| |
6.1 | 445 | anEditingContext.objectsWithFetchSpecification( aFetchSpecification ); |
| 446 | |||
| 447 | {{/code}} | ||
| 448 | |||
| |
7.1 | 449 | You can use FSFile to retrieve files only. FSDirectory for, er, |
| |
8.1 | 450 | directories. And FSItem for both. FSDirectory have two handy |
| 451 | relationships: "files" and "directories". Check the attached model. | ||
| |
6.1 | 452 | |
| |
8.1 | 453 | (% class="alternate" %) |
| |
7.1 | 454 | * To "create" a file (or directory), you can do something along those |
| |
8.1 | 455 | lines: |
| |
6.1 | 456 | |
| 457 | {{code}} | ||
| 458 | |||
| |
7.1 | 459 | EOClassDescription aClassDescription = |
| |
6.1 | 460 | EOClassDescription.classDescriptionForEntityName( "FSDirectory" ); |
| |
7.1 | 461 | EOEnterpriseObject anObject = |
| 462 | aClassDescription.createInstanceWithEditingContext( anEditingContext, | ||
| |
6.1 | 463 | null ); |
| |
7.1 | 464 | |
| |
6.1 | 465 | anObject.takeValueForKey |
| 466 | ( "/Users/rszwarc/tmp/JavaFSAdaptor/TestFSDirectory", "absolutePath" ); | ||
| |
7.1 | 467 | |
| |
6.1 | 468 | anEditingContext.insertObject( anObject ); |
| |
7.1 | 469 | |
| |
6.1 | 470 | anEditingContext.saveChanges(); |
| 471 | |||
| 472 | {{/code}} | ||
| 473 | |||
| |
8.1 | 474 | (% class="alternate" %) |
| 475 | * Finally, do delete a file: | ||
| |
6.1 | 476 | |
| 477 | {{code}} | ||
| 478 | |||
| 479 | anEditingContext.deleteObject( anObject ); | ||
| 480 | |||
| 481 | anEditingContext.saveChanges(); | ||
| 482 | |||
| 483 | {{/code}} |