Wiki source code of EOF-Using EOF-Custom EOAdaptor

Version 4.1 by Pascal Robert on 2009/04/22 09:10

Show last authors
1 It's surprisingly easy to create a simple custom EOAdaptor. In 2002, there was a post to the Omni WebObjects mailing list with the code necessary to do implement a File System EOAdaptor (obviously the WO version is a little old, but it should be a good starting point):
2
3 [[http://www.wodeveloper.com/omniLists/eof/2002/June/msg00053.html]]
4
5 This is the original message, so that it's available in case the mailing list archive is down. JavaFSAdaptor is also part of Project Wonder, and you can also look at JavaMemoryAdaptor, also in Wonder.
6
7 Here is a little example on how the create an EOAdaptor:
8
9 Creating an EOAdaptor can be very straightforward (or very complicated)
10 depending on the underlying data source. In this example, and for the
11 sake of keeping thing simple, I'm using the file system as a data source
12 (well, at least, how java.io.File sees it).
13 At a bare minimum you need to implement three classes: EOAdaptor,
14 EOAdaptorContext, EOAdaptorChannel.
15
16 [[EOAdaptor]]
17
18 Not much in here. The EOAdaptor is mostly a rendezvous point to access
19 other functionality in your adaptor. You need to implement a couple of
20 abstract methods though:
21
22 * assertConnectionDictionaryIsValid() to check the validity of a
23 connection dictionary.
24 * createAdaptorContext() to create your own EOAdaptorContext instance.
25 * defaultExpressionClass() to return a relevant EOSQLExpression class.
26 * isValidQualifierType() to decide if a qualifier can be used in a SQL
27 where clause.
28 * synchronizationFactory() to return a EOSchemaGeneration helper.
29
30 In the case of a file system there is not much to do:
31
32 {{code}}
33
34 public void assertConnectionDictionaryIsValid()
35 {
36 }
37
38 public EOAdaptorContext createAdaptorContext()
39 {
40 return new FSAdaptorContext( this );
41 }
42
43 public Class defaultExpressionClass()
44 {
45 throw new UnsupportedOperationException
46 ( "FSAdaptor.defaultExpressionClass" );
47 }
48
49 public EOSQLExpressionFactory expressionFactory()
50 {
51 throw new UnsupportedOperationException
52 ( "FSAdaptor.expressionFactory" );
53 }
54
55 public boolean isValidQualifierType(String aTypeName, EOModel aModel)
56 {
57 return true;
58 }
59
60 public EOSchemaGeneration synchronizationFactory()
61 {
62 throw new UnsupportedOperationException
63 ( "FSAdaptor.synchronizationFactory" );
64 }
65
66 {{/code}}
67
68
69 The only important method is createAdaptorContext().
70
71 [[EOAdaptorContext]]
72
73 The adaptor context handles "transactions". It's usually pretty simple
74 if your data store support that concept. Not much to do here for a file
75 system:
76
77 {{code}}
78
79 public void beginTransaction()
80 {
81 if ( _hasTransaction == false )
82 {
83 _hasTransaction = true;
84
85 this.transactionDidBegin();
86 }
87 }
88
89 public void commitTransaction()
90 {
91 if ( _hasTransaction == true )
92 {
93 _hasTransaction = false;
94
95 this.transactionDidCommit();
96 }
97 }
98
99 public EOAdaptorChannel createAdaptorChannel()
100 {
101 return new FSAdaptorChannel( this );
102 }
103
104 public void handleDroppedConnection()
105 {
106 }
107
108 public void rollbackTransaction()
109 {
110 throw new UnsupportedOperationException
111 ( "FSAdaptorContext.rollbackTransaction" );
112 }
113
114 {{/code}}
115
116
117 Again createAdaptorChannel() is the only method that you really care
118 about in this case.
119
120 [[EOAdaptorChannel]]
121
122 That's the real thing. It's where most of the work is done. It's
123 important to get this one right. Depending on your back end, it's simply
124 an exercise in translating EOQualifiers into something that your data
125 storage will understand. Here a the most important methods:
126
127 * deleteRowsDescribedByQualifier() to handle deletion of "row" or
128 something equivalent.
129 * insertRow() to create a "row".
130 * updateValuesInRowsDescribedByQualifier() to update a "row".
131
132 Finally, fetching "rows" is a two steps process: first you need to setup
133 the "context" of what you are going to fetch and then fetch it:
134
135 * selectAttributes() is where you set your fetch "context".
136 * fetchRow is the actual fetching one row at the time.
137
138 In the case of a file system here is what you could have:
139
140 * deleteRowsDescribedByQualifier will delete some files (aka "rows"
141 based) on a qualifier.
142
143 {{code}}
144
145 public int deleteRowsDescribedByQualifier(EOQualifier aQualifier,
146 EOEntity anEntity)
147 {
148 if ( aQualifier != null )
149 {
150 if ( anEntity != null )
151 {
152 NSArray someFiles =
153 FSQualifierHandler.filesWithQualifier( aQualifier );
154
155 if ( someFiles != null )
156 {
157 someFiles = this.filteredArrayWithEntity( someFiles, anEntity );
158
159 if ( someFiles != null )
160 {
161 int count = someFiles.count();
162 int counter = 0;
163
164 for ( int index = 0; index < count; index++ )
165 {
166 File aFile = (File) someFiles.objectAtIndex( index );
167
168 if ( aFile.delete() == true )
169 {
170 counter += 1;
171 }
172 }
173
174 return counter;
175 }
176 }
177
178 return 0;
179 }
180
181 throw new IllegalArgumentException
182 ( "FSAdaptorChannel.deleteRowsDescribedByQualifier: null entity." );
183 }
184
185 throw new IllegalArgumentException
186 ( "FSAdaptorChannel.deleteRowsDescribedByQualifier: null qualifier." );
187 }
188
189 {{/code}}
190
191 * insertRow will create a file or directory.
192
193 {{code}}
194
195 public void insertRow(NSDictionary aRow, EOEntity anEntity)
196 {
197 if ( aRow != null )
198 {
199 if ( anEntity != null )
200 {
201 String aPath = (String) aRow.objectForKey( "absolutePath" );
202
203 if ( aPath != null )
204 {
205 File aFile = new File( aPath );
206
207 try
208 {
209 if ( anEntity.name().equals( "FSDirectory" ) == true )
210 {
211 aFile.mkdirs();
212 }
213 else
214 {
215 aFile.createNewFile();
216 }
217 }
218 catch(Exception anException)
219 {
220 throw new RuntimeException
221 ( "FSAdaptorChannel.insertRow: " + anException );
222 }
223
224 return;
225 }
226
227 throw new IllegalArgumentException
228 ( "FSAdaptorChannel.insertRow: null absolutePath." );
229 }
230
231 throw new IllegalArgumentException
232 ( "FSAdaptorChannel.insertRow: null entity." );
233 }
234
235 throw new IllegalArgumentException
236 ( "FSAdaptorChannel.insertRow: null row." );
237 }
238
239 {{/code}}
240
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 * selectAttributes will retrieve some files based on a qualifier.
313
314 {{code}}
315
316 public void selectAttributes(NSArray someAttributes,
317 EOFetchSpecification aFetchSpecification, boolean shouldLock, EOEntity
318 anEntity)
319 {
320 if ( someAttributes != null )
321 {
322 this.setAttributesToFetch( someAttributes );
323
324 if ( aFetchSpecification != null )
325 {
326 if ( anEntity != null )
327 {
328 NSArray someFiles =
329 FSQualifierHandler.filesWithQualifier( aFetchSpecification.qualifier() );
330
331 if ( someFiles != null )
332 {
333 NSArray someSortOrderings = aFetchSpecification.sortOrderings();
334
335 if ( someSortOrderings != null )
336 {
337 someFiles =
338 EOSortOrdering.sortedArrayUsingKeyOrderArray( someFiles,
339 someSortOrderings );
340 }
341
342 someFiles = this.filteredArrayWithEntity( someFiles, anEntity );
343
344 if ( someFiles != null )
345 {
346 this.files().addObjectsFromArray( someFiles );
347 }
348 }
349
350 return;
351 }
352
353 throw new IllegalArgumentException
354 ( "FSAdaptorChannel.selectAttributes: null entity." );
355 }
356
357 throw new IllegalArgumentException
358 ( "FSAdaptorChannel.selectAttributes: null fetch specification." );
359 }
360
361 throw new IllegalArgumentException
362 ( "FSAdaptorChannel.selectAttributes: null attributes." );
363 }
364
365 {{/code}}
366
367
368
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
391
392 That could be it. Nothing to involved. However there is two extra
393 methods that make sense to implement: describeTableNames and
394 describeModelWithTableNames.
395
396 * describeTableNames() return an array of "table" (or something
397 conceptually equivalent) existing in you data back end.
398 * describeModelWithTableNames() will create a default model for some of
399 these "tables".
400
401 This is very useful to get you started in EOModeler instead of having to
402 create the entire model by hand. In the case of a file system, there is
403 only one model:
404
405 {{code}}
406
407 public EOModel describeModelWithTableNames(NSArray anArray)
408 {
409 return new EOModel( this.defaultModelPath() );
410 }
411
412 {{/code}}
413
414 And voilĂ . No you have a toy file system adaptor (aka JavaFSAdaptor).
415
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 * To get all the files in a directory, use the "parent" attribute:
428
429 {{code}}
430
431 EOQualifier aPathQualifier = new EOKeyValueQualifier( "parent",
432 EOQualifier.QualifierOperatorEqual, System.getProperty( "user.home" ) );
433
434 {{/code}}
435
436 Once you have your qualifier, simply build a fetch specification for it
437 and get your "file" EOs:
438
439 {{code}}
440
441 EOFetchSpecification aFetchSpecification = new EOFetchSpecification
442 ( "FSDirectory", aQualifier, null );
443 NSArray someObjects =
444 anEditingContext.objectsWithFetchSpecification( aFetchSpecification );
445
446 {{/code}}
447
448 You can use FSFile to retrieve files only. FSDirectory for, er,
449 directories. And FSItem for both. FSDirectory have two handy
450 relationships: "files" and "directories". Check the attached model.
451
452 * To "create" a file (or directory), you can do something along those
453 lines:
454
455 {{code}}
456
457 EOClassDescription aClassDescription =
458 EOClassDescription.classDescriptionForEntityName( "FSDirectory" );
459 EOEnterpriseObject anObject =
460 aClassDescription.createInstanceWithEditingContext( anEditingContext,
461 null );
462
463 anObject.takeValueForKey
464 ( "/Users/rszwarc/tmp/JavaFSAdaptor/TestFSDirectory", "absolutePath" );
465
466 anEditingContext.insertObject( anObject );
467
468 anEditingContext.saveChanges();
469
470 {{/code}}
471
472 * Finally, do delete a file:
473
474 {{code}}
475
476 anEditingContext.deleteObject( anObject );
477
478 anEditingContext.saveChanges();
479
480 {{/code}}