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}} |