appengine-apis 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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