appengine-apis 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ == 0.0.1 2009-04-02
2
+
3
+ * Initial release
@@ -0,0 +1,21 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README.rdoc
5
+ Rakefile
6
+ lib/appengine-apis.rb
7
+ lib/appengine-apis/apiproxy.rb
8
+ lib/appengine-apis/datastore.rb
9
+ lib/appengine-apis/datastore_types.rb
10
+ lib/appengine-apis/logger.rb
11
+ lib/appengine-apis/merb-logger.rb
12
+ lib/appengine-apis/testing.rb
13
+ script/console
14
+ script/destroy
15
+ script/generate
16
+ spec/datastore_spec.rb
17
+ spec/datastore_types_spec.rb
18
+ spec/logger_spec.rb
19
+ spec/spec.opts
20
+ spec/spec_helper.rb
21
+ tasks/rspec.rake
@@ -0,0 +1,12 @@
1
+
2
+ To use the local datastore you need to have the Google App Engine SDK for Java
3
+ installed
4
+ (http://code.google.com/appengine/docs/java/gettingstarted/installing.html).
5
+ Then you need to add several jars to your classpath.
6
+
7
+ For example:
8
+ $ export SDK=`pwd`/appengine-java-sdk/lib
9
+ $ export CLASSPATH=$SDK/shared/appengine-local-runtime-shared.jar:$SDK/impl/appengine-api-stubs.jar:$SDK/impl/appengine-api.jar:$SDK/impl/appengine-local-runtime.jar
10
+
11
+ For more information on appengine-apis, see
12
+ http://code.google.com/p/appengine-jruby
@@ -0,0 +1,27 @@
1
+ = appengine-apis
2
+
3
+ * http://code.google.com/p/appengine-jruby
4
+
5
+ == DESCRIPTION:
6
+
7
+ APIs and utilities for using JRuby on Google App Engine.
8
+
9
+ == REQUIREMENTS:
10
+
11
+ * Google App Engine SDK for Java (http://code.google.com/appengine)
12
+
13
+ == LICENSE:
14
+
15
+ Copyright 2009 Google Inc
16
+
17
+ Licensed under the Apache License, Version 2.0 (the "License");
18
+ you may not use this file except in compliance with the License.
19
+ You may obtain a copy of the License at
20
+
21
+ http://www.apache.org/licenses/LICENSE-2.0
22
+
23
+ Unless required by applicable law or agreed to in writing, software
24
+ distributed under the License is distributed on an "AS IS" BASIS,
25
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26
+ See the License for the specific language governing permissions and
27
+ limitations under the License.
@@ -0,0 +1,28 @@
1
+ %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/appengine-apis'
3
+
4
+ # Generate all the Rake tasks
5
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
+ $hoe = Hoe.new('appengine-apis', AppEngine::VERSION) do |p|
7
+ p.developer('Ryan Brown', 'ribrdb@gmail.com')
8
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
+ p.post_install_message = 'PostInstall.txt'
10
+ p.rubyforge_name = 'appengine-jruby'
11
+ # p.extra_deps = [
12
+ # ['activesupport','>= 2.0.2'],
13
+ # ]
14
+ p.extra_dev_deps = [
15
+ ['newgem', ">= #{::Newgem::VERSION}"]
16
+ ]
17
+
18
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
19
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
20
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
21
+ p.rsync_args = '-av --delete --ignore-errors'
22
+ end
23
+
24
+ require 'newgem/tasks' # load /tasks/*.rake
25
+ Dir['tasks/**/*.rake'].each { |t| load t }
26
+
27
+ # TODO - want other tests/tasks run by default? Add them to the list
28
+ # task :default => [:spec, :features]
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/ruby1.8 -w
2
+ #
3
+ # Copyright:: Copyright 2009 Google Inc.
4
+ # Original Author:: Ryan Brown (mailto:ribrdb@google.com)
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ $:.unshift(File.dirname(__FILE__)) unless
19
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
20
+
21
+ module AppEngine
22
+ VERSION = '0.0.1'
23
+ end
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/ruby1.8 -w
2
+ #
3
+ # Copyright:: Copyright 2009 Google Inc.
4
+ # Original Author:: Ryan Brown (mailto:ribrdb@google.com)
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ #
19
+ # Ruby interface to the Java ApiProxy.
20
+
21
+ require 'java'
22
+
23
+ module AppEngine
24
+
25
+ import Java.com.google.apphosting.api.ApiProxy
26
+
27
+ class << ApiProxy
28
+ def get_app_id
29
+ get_current_environment.getAppId
30
+ end
31
+
32
+ def get_auth_domain
33
+ get_current_environment.getAuthDomain
34
+ end
35
+
36
+ alias :add_log_record :log
37
+
38
+ def log(level, message)
39
+ message = (message || "").to_s.chomp
40
+ return if message.nil? || message.empty?
41
+ record = AppEngine::ApiProxy::LogRecord.new(
42
+ level, java.lang.System.currentTimeMillis() * 1000, message.to_s)
43
+ add_log_record(record)
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,515 @@
1
+ #!/usr/bin/ruby1.8 -w
2
+ #
3
+ # Copyright:: Copyright 2009 Google Inc.
4
+ # Original Author:: Ryan Brown (mailto:ribrdb@google.com)
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ #
19
+ # The Ruby datastore API used by app developers.
20
+ #
21
+ # Defines the Query class, as well as methods for all of the
22
+ # datastore's calls. Also defines conversions between the Ruby classes and
23
+ # their Java counterparts.
24
+ #
25
+ # The datastore errors are defined in datastore_types.rb.
26
+
27
+ require 'appengine-apis/apiproxy'
28
+ require 'appengine-apis/datastore_types'
29
+
30
+ module AppEngine
31
+
32
+ # The +Datastore+ provides access to a schema-less data
33
+ # storage system. The fundamental unit of data in this system is the
34
+ # +Entity+, which has an immutable identity (represented by a
35
+ # +Key+) and zero of more mutable properties. +Entity+
36
+ # objects can be created, updated, deleted, retrieved by identifier,
37
+ # and queried via a combination of properties.
38
+ #
39
+ # The +Datastore+ can be used transactionally and supports the
40
+ # notion of a "current" transaction. A current transaction is established by
41
+ # calling #begin_transaction. The transaction returned by this method
42
+ # ceases to be current when an attempt is made to commit or rollback or when
43
+ # another call is made to #begin_transaction. A transaction can only
44
+ # be current within the +Thread+ that created it.
45
+ #
46
+ # The various overloads of put, get, and delete all support transactions.
47
+ # Users of this class have the choice of explicitly passing a (potentially
48
+ # null) +Transaction+ to these methods or relying on the current transaction.
49
+ #
50
+ module Datastore
51
+ module_function
52
+
53
+ # call-seq:
54
+ # Datastore.get(transaction=current_transaction, key) -> Entity
55
+ # Datastore.get(transaction=current_transaction, [keys]) -> Entities
56
+ #
57
+ # Retrieves one or more entities from the datastore.
58
+ #
59
+ # Retrieves the entity or entities with the given key(s) from the datastore
60
+ # and returns them as fully populated Entity objects, as defined below. If
61
+ # there is an error, raises a subclass of Datastore::Error.
62
+ #
63
+ # With a single key, an Entity will be returned, or
64
+ # EntityNotFound will be raised if no existing entity matches the key.
65
+ #
66
+ # With an array of keys, an array of entities will be returned that
67
+ # corresponds to the sequence of keys. It will include entities for keys
68
+ # that were found and None placeholders for keys that were not found.
69
+ #
70
+ # If transaction is specified, it will be used instead of the
71
+ # current transaction.
72
+ #
73
+ def get(*args)
74
+ convert_exceptions do
75
+ args = extract_tx(args)
76
+ entities = @@db.get(*args)
77
+ if entities.kind_of? java.util.Map
78
+ keys = args[-1]
79
+ entities = keys.collect do |key|
80
+ entities.get(key)
81
+ end
82
+ end
83
+ entities
84
+ end
85
+ end
86
+
87
+ # call-seq:
88
+ # Datastore.put(transaction=current_transaction, entity) -> Key
89
+ # Datastore.put(transaction=current_transaction, entities) -> Keys
90
+ #
91
+ # Store one or more entities in the datastore.
92
+ #
93
+ # The entities may be new or previously existing. For new entities, #put will
94
+ # fill in the app id and key assigned by the datastore.
95
+ #
96
+ # If the argument is a single Entity, a single Key will be returned. If the
97
+ # argument is an array of Entity, an Enumerable of Keys will be returned.
98
+ #
99
+ # If transaction is specified this operation will execute within that
100
+ # transaction instead of the current transaction.
101
+ #
102
+ def put(*args)
103
+ convert_exceptions do
104
+ args = extract_tx(args)
105
+ @@db.put(*args)
106
+ end
107
+ end
108
+
109
+ # call-seq:
110
+ # Datastore.delete(transaction=current_transaction, key)
111
+ # Datastore.delete(transaction=current_transaction, [keys])
112
+ #
113
+ # Deletes one or more entities from the datastore.
114
+ #
115
+ # If transaction is specified this operation will execute within that
116
+ # transaction instead of the current transaction.
117
+ #
118
+ def delete(*args)
119
+ convert_exceptions do
120
+ args = extract_tx(args)
121
+ @@db.delete(*args)
122
+ end
123
+ end
124
+
125
+ # Begins a transaction agains the datastore. Callers are
126
+ # responsible for explicitly calling #Transaction.commit or
127
+ # #Transaction.rollback when they no longer need the Transaction.
128
+ #
129
+ # The Transaction returned by this call will be considered the
130
+ # current transaction and will be returned by subsequent, same-thread
131
+ # calls to #current_transaction until one of the following happens:
132
+ #
133
+ # 1. #begin_transaction is invoked from the same thread. In this case
134
+ # #current_transaction will return the result of the more recent
135
+ # call to #begin_transaction.
136
+ # 2. #Transaction.commit is invoked on the Transaction returned by
137
+ # this method. Whether or not the commit succeeds, the
138
+ # Transaction will no longer be current.
139
+ # 3. #Transaction.rollback is invoked on the Transaction returned by
140
+ # this method. Whether or not the rollback succeeds, the
141
+ # Transaction will no longer be current.
142
+ #
143
+ def begin_transaction
144
+ convert_exceptions do
145
+ @@db.begin_transaction
146
+ end
147
+ end
148
+
149
+ # call-seq:
150
+ # Datastore.current_transaction -> transaction || IndexError
151
+ # Datastore.current_transaction(default) -> transaction
152
+ #
153
+ # Returns the current transaction for this thread. The current transaction
154
+ # is defined as the result of the most recent, same-thread invocation of
155
+ # #begin_transaction that has not been committed or rolled back.
156
+ #
157
+ # Raises IndexError if there is no current transaction and no default
158
+ # is specified.
159
+ #
160
+ def current_transaction(*args)
161
+ convert_exceptions do
162
+ @@db.current_transaction(*args)
163
+ end
164
+ end
165
+
166
+ # Returns all Transactions started by this thread upon which no
167
+ # attempt to commit or rollback has been made.
168
+ #
169
+ def active_transactions
170
+ convert_exceptions do
171
+ @@db.active_transactions
172
+ end
173
+ end
174
+
175
+ # Runs the block inside a transaction. Every #get, #put, and #delete
176
+ # call in the block is made within the transaction, unless another
177
+ # transaction is explicitly specified.
178
+ #
179
+ # The block may raise any exception to roll back the transaction instead of
180
+ # committing it. If this happens, the transaction will be rolled back and the
181
+ # exception will be re-raised up to #transaction's caller.
182
+ #
183
+ # If you want to roll back intentionally, but don't have an appropriate
184
+ # exception to raise, you can raise an instance of Datastore::Rollback.
185
+ # It will cause a rollback, but will *not* be re-raised up to the caller.
186
+ #
187
+ # If retries is greater than 0 and the transaction fails to commit,
188
+ # the block may be run more than once, so it should be idempotent. It
189
+ # should avoid side effects, and it shouldn't have *any* side effects that
190
+ # aren't safe to occur multiple times. However, this doesn't
191
+ # include Put, Get, and Delete calls, of course.
192
+ #
193
+ def transaction(retries=0)
194
+ while retries >= 0
195
+ retries -= 1
196
+ tx = begin_transaction
197
+ begin
198
+ result = yield
199
+ tx.commit
200
+ return result
201
+ rescue Rollback
202
+ tx.rollback
203
+ return nil
204
+ rescue TransactionFailed
205
+ raise ex unless retries >= 0
206
+ ensure
207
+ begin
208
+ tx.rollback
209
+ rescue java.lang.IllegalStateException
210
+ # already commited/rolled back. ignore
211
+ rescue java.util.NoSuchElementException
212
+ # already commited/rolled back. ignore
213
+ end
214
+ end
215
+ end
216
+ raise TransactionFailed
217
+ end
218
+
219
+ def extract_tx(args) # :nodoc:
220
+ tx = :none
221
+ keys = args[0]
222
+ if keys.java_kind_of?(JavaDatastore::Transaction) || keys.nil?
223
+ tx = args.shift
224
+ keys = args[0]
225
+ end
226
+ if args.size > 1
227
+ keys = args
228
+ end
229
+ if keys.kind_of? Array
230
+ keys = Iterable.new(keys)
231
+ end
232
+ if tx == :none
233
+ [keys]
234
+ else
235
+ [tx, keys]
236
+ end
237
+ end
238
+
239
+ def service # :nodoc:
240
+ @@db
241
+ end
242
+
243
+ # Query encapsulates a request for zero or more
244
+ # Entity objects out of the datastore. It supports querying on
245
+ # zero or more properties, querying by ancestor, and sorting.
246
+ # Entity objects which match the query can be retrieved in a single
247
+ # list, or with an unbounded iterator.
248
+ #
249
+ # A Query does not cache results. Each use of the Query results in a new
250
+ # trip to the Datastore.
251
+ #
252
+ class Query
253
+ JQuery = JavaDatastore::Query
254
+ FetchOptions = JavaDatastore::FetchOptions
255
+
256
+ module Constants
257
+ [JQuery::FilterOperator, JQuery::SortDirection].each do |enum|
258
+ enum.constants.each do |name|
259
+ const_set(name, enum.const_get(name))
260
+ end
261
+ end
262
+ end
263
+ include Constants
264
+
265
+ # call-seq:
266
+ # Query.new(kind)
267
+ # Query.new(ancestor)
268
+ # Query.new(kind, ancestor)
269
+ #
270
+ # Creates a new Query with the specified kind and/or ancestor.
271
+ #
272
+ # Args:
273
+ # - kind: String. Only return entities with this kind.
274
+ # - ancestor: Key. Only return entities with the given ancestor.
275
+ #
276
+ def initialize(*args)
277
+ @query = JQuery.new(*args)
278
+ end
279
+
280
+ def kind
281
+ @query.kind
282
+ end
283
+
284
+
285
+ def ancestor
286
+ @query.ancestor
287
+ end
288
+
289
+ # Sets an ancestor for this query.
290
+ #
291
+ # This restricts the query to only return result entities that are
292
+ # descended from a given entity. In other words, all of the results
293
+ # will have the ancestor as their parent, or parent's parent, or
294
+ # etc.
295
+ #
296
+ # If nil is specified, unsets any previously-set ancestor.
297
+ #
298
+ # Throws ArgumentError if the ancestor key is incomplete, or if
299
+ # you try to unset an ancestor and have not set a kind.
300
+ #
301
+ def ancestor=(key)
302
+ Datastore.convert_exceptions do
303
+ @query.set_ancestor(key)
304
+ end
305
+ clear_cache
306
+ end
307
+
308
+ # call-seq:
309
+ # query.set_ancestor(key) -> query
310
+ #
311
+ # Sets an ancestor for this query.
312
+ #
313
+ # This restricts the query to only return result entities that are
314
+ # descended from a given entity. In other words, all of the results
315
+ # will have the ancestor as their parent, or parent's parent, or
316
+ # etc.
317
+ #
318
+ # If nil is specified, unsets any previously-set ancestor.
319
+ #
320
+ # Throws ArgumentError if the ancestor key is incomplete, or if
321
+ # you try to unset an ancestor and have not set a kind.
322
+ #
323
+ def set_ancestor(key)
324
+ self.ancestor = key
325
+ self
326
+ end
327
+
328
+ # Add a filter on the specified property.
329
+ #
330
+ # Note that entities with multi-value properties identified by name
331
+ # will match this filter if the multi-value property has at least one
332
+ # value that matches the condition expressed by +operator+ and
333
+ # +value+. For more information on multi-value property filtering
334
+ # please see the {datastore
335
+ # documentation}[http://code.google.com/appengine/docs/java/datastore]
336
+ #
337
+ def filter(name, operator, value)
338
+ name = name.to_s if name.kind_of? Symbol
339
+ value = Datastore.ruby_to_java(value)
340
+ @query.add_filter(name, operator, value)
341
+ clear_cache
342
+ end
343
+
344
+ # Specify how the query results should be sorted.
345
+ #
346
+ # The first call to #sort will register the property that will
347
+ # serve as the primary sort key. A second call to #sort will set
348
+ # a secondary sort key, etc.
349
+ #
350
+ # This method will sort in ascending order by defaul. To control the
351
+ # order of the sort, pass ASCENDING or DESCENDING as the direction.
352
+ #
353
+ # Note that entities with multi-value properties identified by
354
+ # name will be sorted by the smallest value in the list.
355
+ # For more information on sorting properties with multiple values please see
356
+ # the {datastore
357
+ # documentation}[http://code.google.com/appengine/docs/java/datastore].
358
+ #
359
+ # Returns self (for chaining)
360
+ #
361
+ def sort(name, direction=ASCENDING)
362
+ name = name.to_s if name.kind_of? Symbol
363
+ @query.add_sort(name, direction)
364
+ clear_cache
365
+ end
366
+
367
+ # Returns an unmodifiable list of the current filter predicates.
368
+ def filter_predicates
369
+ @query.getFilterPredicates
370
+ end
371
+
372
+ # Returns an unmodifiable list of the current sort predicates.
373
+ def sort_predicates
374
+ @query.getSortPredicates
375
+ end
376
+
377
+ # Returns the number of entities that currently match this query.
378
+ def count
379
+ pquery.count
380
+ end
381
+
382
+ # Retrieves the one and only result for the {@code Query}.
383
+ #
384
+ # Throws TooManyResults if more than one result is returned
385
+ # from the Query.
386
+ #
387
+ # Returns the single, matching result, or nil if no entities match
388
+ #
389
+ def entity
390
+ Datastore.convert_exceptions do
391
+ pquery.as_single_entity
392
+ end
393
+ end
394
+
395
+
396
+ # Streams the matching entities from the datastore and yields each
397
+ # matching entity.
398
+ #
399
+ # See #convert_options for supported options
400
+ def each(options={}, &proc) # :yields: entity
401
+ options = convert_options(options)
402
+ Datastore.convert_exceptions do
403
+ pquery.as_iterator(options).each(&proc)
404
+ end
405
+ end
406
+
407
+ # Returns an Enumerable over the matching entities.
408
+ #
409
+ # See #convert_options for supported options
410
+ #
411
+ def iterator(options={})
412
+ options = convert_options(options)
413
+ Datastore.convert_exceptions do
414
+ pquery.as_iterator(options)
415
+ end
416
+ end
417
+
418
+ # Fetch all matching entities. For large result sets you should
419
+ # prefer #each or #iterator, which stream the results from the
420
+ # datastore.
421
+ #
422
+ def fetch(options={})
423
+ options = convert_options(options)
424
+ Datastore.convert_exceptions do
425
+ pquery.as_list(options)
426
+ end
427
+ end
428
+
429
+ # Returns a Java.ComGoogleAppengineApiDatastore.PreparedQuery
430
+ # for this query.
431
+ #
432
+ def pquery
433
+ @pquery ||= Datastore.service.prepare(@query)
434
+ end
435
+
436
+ # Returns a Java.ComGoogleAppengineApiDatastore.Query for this query.
437
+ def java_query
438
+ @query
439
+ end
440
+
441
+ # Converts an options hash into FetchOptions.
442
+ #
443
+ # Supported options:
444
+ # [:limit] Maximum number of results the query will return
445
+ # [:offset]
446
+ # Number of result to skip before returning any
447
+ # results. Results that are skipped due to offset do not count
448
+ # against +limit+.
449
+ # [:chunk]
450
+ # Determines the internal chunking strategy of the iterator
451
+ # returned by #iterator. This affects only the performance of
452
+ # the query, not the actual results returned.
453
+ #
454
+ def convert_options(options)
455
+ return options if options.java_kind_of? FetchOptions
456
+ limit = options.delete(:limit)
457
+ offset = options.delete(:offset)
458
+ chunk_size = options.delete(:chunk) || FetchOptions::DEFAULT_CHUNK_SIZE
459
+ unless options.empty?
460
+ raise ArgumentError, "Unsupported options #{options.inspect}"
461
+ end
462
+ options = FetchOptions::Builder.with_chunk_size(chunk_size)
463
+ options.offset(offset) if offset
464
+ options.limit(limit) if limit
465
+ options
466
+ end
467
+
468
+ private
469
+ def clear_cache
470
+ @pquery = nil
471
+ self
472
+ end
473
+ end
474
+
475
+ class Iterable # :nodoc:
476
+ include java.lang.Iterable
477
+ def initialize(array)
478
+ @array = array
479
+ end
480
+
481
+ def iterator
482
+ Iterator.new(@array)
483
+ end
484
+ end
485
+
486
+ class Iterator # :nodoc:
487
+ include java.util.Iterator
488
+ def initialize(array)
489
+ @array = array
490
+ @index = 0
491
+ @removed = false
492
+ end
493
+
494
+ def hasNext
495
+ @index < @array.size
496
+ end
497
+
498
+ def next
499
+ raise java.util.NoSuchElementException unless hasNext
500
+ @removed = false
501
+ @index += 1
502
+ @array[@index - 1]
503
+ end
504
+
505
+ def remove
506
+ raise java.lang.IllegalStateException if @removed
507
+ raise java.lang.IllegalStateException unless @index > 0
508
+ @removed = true
509
+ @array.delete_at(@index - 1)
510
+ end
511
+ end
512
+
513
+ @@db ||= JavaDatastore::DatastoreServiceFactory.getDatastoreService
514
+ end
515
+ end