Wiki source code of EOF-Using EOF-Custom EOAdaptor

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