Wiki source code of EOF-Using EOF-Custom EOAdaptor

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

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