appengine-apis 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +3 -0
- data/Manifest.txt +21 -0
- data/PostInstall.txt +12 -0
- data/README.rdoc +27 -0
- data/Rakefile +28 -0
- data/lib/appengine-apis.rb +23 -0
- data/lib/appengine-apis/apiproxy.rb +47 -0
- data/lib/appengine-apis/datastore.rb +515 -0
- data/lib/appengine-apis/datastore_types.rb +340 -0
- data/lib/appengine-apis/logger.rb +69 -0
- data/lib/appengine-apis/merb-logger.rb +55 -0
- data/lib/appengine-apis/testing.rb +123 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/datastore_spec.rb +150 -0
- data/spec/datastore_types_spec.rb +198 -0
- data/spec/logger_spec.rb +68 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +11 -0
- data/tasks/rspec.rake +21 -0
- metadata +97 -0
@@ -0,0 +1,340 @@
|
|
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 wrappers for the Java semantic data types for the datastore. These types
|
20
|
+
# are expected to be set as attributes of Entities.
|
21
|
+
|
22
|
+
require 'java'
|
23
|
+
|
24
|
+
class Time
|
25
|
+
def to_java
|
26
|
+
java.util.Date.new(to_i * 1000)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.new_from_java(date)
|
30
|
+
at(date.time / 1000)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module AppEngine
|
35
|
+
module Datastore
|
36
|
+
JavaDatastore = Java.ComGoogleAppengineApiDatastore
|
37
|
+
|
38
|
+
class Error < StandardError; end
|
39
|
+
class NeedIndex < Error; end
|
40
|
+
class Timeout < Error; end
|
41
|
+
class InternalError < Error; end
|
42
|
+
class Rollback < Error; end
|
43
|
+
class TransactionFailed < Error; end
|
44
|
+
class EntityNotFound < Error; end
|
45
|
+
class TooManyResults < Error; end
|
46
|
+
|
47
|
+
#A long string type.
|
48
|
+
#
|
49
|
+
# Strings of any length can be stored in the datastore using this
|
50
|
+
# type.
|
51
|
+
#
|
52
|
+
class Text < String
|
53
|
+
def to_java
|
54
|
+
JavaDatastore::Text.new(self.to_java_string)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.new_from_java(text)
|
58
|
+
self.new(text.getValue)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# A blob type, appropriate for storing binary data of any length.
|
63
|
+
class Blob < String
|
64
|
+
def to_java
|
65
|
+
JavaDatastore::Blob.new(self.to_java_bytes)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.new_from_java(blob)
|
69
|
+
self.new(self.from_java_bytes(blob.getBytes))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# A byte-string type, appropriate for storing short amounts of indexed data.
|
74
|
+
#
|
75
|
+
# This behaves identically to Blob, except it's used only for short, indexed
|
76
|
+
# byte strings.
|
77
|
+
#
|
78
|
+
class ByteString < Blob
|
79
|
+
def to_java
|
80
|
+
JavaDatastore::ShortBlob.new(self.to_java_bytes)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# A fully qualified URL. Usually http: scheme, but may also be file:, ftp:,
|
85
|
+
# news:, among others.
|
86
|
+
#
|
87
|
+
class Link < String
|
88
|
+
def to_java
|
89
|
+
JavaDatastore::Link.new(self.to_java_string)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.new_from_java(link)
|
93
|
+
self.new(link.getValue)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
Key = JavaDatastore::Key
|
98
|
+
|
99
|
+
# The primary key for a datastore entity.
|
100
|
+
#
|
101
|
+
# A datastore GUID. A Key instance uniquely identifies an entity
|
102
|
+
# across all apps, and includes all information necessary to fetch
|
103
|
+
# the entity from the datastore with #Datastore.get(Key).
|
104
|
+
#
|
105
|
+
# See http://code.google.com/appengine/docs/java/javadoc/com/google/appengine/api/datastore/Key.html
|
106
|
+
#
|
107
|
+
class Key
|
108
|
+
|
109
|
+
# Converts a Key into a websafe string. For example, this string
|
110
|
+
# can safely be used as an URL parameter embedded in a HTML document.
|
111
|
+
#
|
112
|
+
def to_s
|
113
|
+
JavaDatastore::KeyFactory.keyToString(self)
|
114
|
+
end
|
115
|
+
|
116
|
+
alias :inspect :to_string
|
117
|
+
alias :id :get_id
|
118
|
+
alias :== :equals?
|
119
|
+
|
120
|
+
def id_or_name
|
121
|
+
name || id
|
122
|
+
end
|
123
|
+
|
124
|
+
class << self
|
125
|
+
|
126
|
+
# Creates a new Key from an encoded String.
|
127
|
+
def new(encoded)
|
128
|
+
JavaDatastore::KeyFactory.stringToKey(encoded)
|
129
|
+
end
|
130
|
+
|
131
|
+
# call-seq:
|
132
|
+
# Key.from_path(parent=nil, kind, id, [kind, id]...) -> Key
|
133
|
+
# Constructs a Key out of a path.
|
134
|
+
#
|
135
|
+
# This is useful when an application wants to use just the 'id'
|
136
|
+
# portion of a key in e.g. a URL, where the rest of the URL
|
137
|
+
# provides enough context to fill in the rest, i.e. the app id
|
138
|
+
# (always implicit), the entity kind, and possibly an ancestor
|
139
|
+
# key. Since the 'id' is a relatively small int, it is more
|
140
|
+
# attractive for use in end-user-visible URLs than the full
|
141
|
+
# string representation of a key.
|
142
|
+
#
|
143
|
+
# Args:
|
144
|
+
# - parent: Optional parent key
|
145
|
+
# - kind: the entity kind (a string)
|
146
|
+
# - id: the id (an integer)
|
147
|
+
# - Additional, optional 'kind' and 'id' arguments are allowed in
|
148
|
+
# an alternating order (kind1, 1, kind2, 2, ...)
|
149
|
+
# - options: a Hash. If specified, options[:parent] is used as
|
150
|
+
# the parent Key.
|
151
|
+
#
|
152
|
+
# Returns:
|
153
|
+
# - A new Key instance whose #kind and #id methods return the *last*
|
154
|
+
# kind and id arugments passed
|
155
|
+
#
|
156
|
+
def from_path(parent_or_kind, kind_or_id, *args)
|
157
|
+
# Extract parent
|
158
|
+
parent = nil
|
159
|
+
if parent_or_kind.is_a? Key
|
160
|
+
parent = parent_or_kind
|
161
|
+
args[0,0] = [kind_or_id]
|
162
|
+
else
|
163
|
+
args[0,0] = [parent_or_kind, kind_or_id]
|
164
|
+
end
|
165
|
+
|
166
|
+
if args.size % 2 != 0
|
167
|
+
raise ArgumentError, 'Expected an even number of arguments ' \
|
168
|
+
'(kind1, id1, kind2, id2, ...); received ' \
|
169
|
+
"#{args.inspect}"
|
170
|
+
end
|
171
|
+
|
172
|
+
# Type-check parent
|
173
|
+
if parent
|
174
|
+
unless parent.is_a? Key
|
175
|
+
raise ArgumentError, 'Expected nil or a Key as a parent; ' \
|
176
|
+
"received #{parent} (a #{parent.class})."
|
177
|
+
end
|
178
|
+
unless parent.is_complete?
|
179
|
+
raise KeyError, 'The parent key has not yet been Put.'
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
current = parent
|
184
|
+
(0...args.size).step(2) do |i|
|
185
|
+
kind, id = args[i,2]
|
186
|
+
kind = kind.to_s if kind.is_a? Symbol
|
187
|
+
if current
|
188
|
+
current = current.getChild(kind, id)
|
189
|
+
else
|
190
|
+
current = JavaDatastore::KeyFactory.createKey(kind, id)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
return current
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
Entity = JavaDatastore::Entity
|
200
|
+
|
201
|
+
# Entity is the fundamental unit of data storage. It has an
|
202
|
+
# immutable identifier (contained in the Key) object, a
|
203
|
+
# reference to an optional parent Entity, a kind (represented
|
204
|
+
# as an arbitrary string), and a set of zero or more typed
|
205
|
+
# properties.
|
206
|
+
#
|
207
|
+
# See http://code.google.com/appengine/docs/java/javadoc/com/google/appengine/api/datastore/Entity.html
|
208
|
+
#
|
209
|
+
class Entity
|
210
|
+
include Enumerable
|
211
|
+
|
212
|
+
SPECIAL_JAVA_TYPES = {
|
213
|
+
JavaDatastore::Text => Text,
|
214
|
+
JavaDatastore::Blob => Blob,
|
215
|
+
JavaDatastore::ShortBlob => ByteString,
|
216
|
+
JavaDatastore::Link => Link,
|
217
|
+
java.util.Date => Time,
|
218
|
+
}.freeze
|
219
|
+
|
220
|
+
alias :inspect :to_string
|
221
|
+
alias :== :equals?
|
222
|
+
|
223
|
+
# Returns the property with the specified name.
|
224
|
+
def get_property(name)
|
225
|
+
name = name.to_s if name.kind_of? Symbol
|
226
|
+
prop = Datastore.convert_exceptions { getProperty(name) }
|
227
|
+
ruby_type = SPECIAL_JAVA_TYPES[prop.class]
|
228
|
+
if ruby_type
|
229
|
+
ruby_type.new_from_java(prop)
|
230
|
+
else
|
231
|
+
prop
|
232
|
+
end
|
233
|
+
end
|
234
|
+
alias :[] :get_property
|
235
|
+
|
236
|
+
# Sets the property named, +name+, to +value+.
|
237
|
+
#
|
238
|
+
# As the value is stored in the datastore, it is converted to the
|
239
|
+
# datastore's native type.
|
240
|
+
#
|
241
|
+
# All Enumerables are prone to losing their sort order and their
|
242
|
+
# original types as they are stored in the datastore. For example, a
|
243
|
+
# Set may be returned as an Array from #getProperty, with an
|
244
|
+
# arbitrary re-ordering of elements.
|
245
|
+
#
|
246
|
+
# +value+ may be one of the supported datatypes, or a heterogenous
|
247
|
+
# Enumerable of one of the supported datatypes.
|
248
|
+
#
|
249
|
+
# Throws ArgumentError if the value is not of a type that
|
250
|
+
# the data store supports.
|
251
|
+
#
|
252
|
+
def set_property(name, value)
|
253
|
+
name = name.to_s if name.kind_of? Symbol
|
254
|
+
value = Datastore.ruby_to_java(value)
|
255
|
+
Datastore.convert_exceptions do
|
256
|
+
setProperty(name, value)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
alias :[]= :set_property
|
260
|
+
|
261
|
+
# Removes any property with the specified name. If there is no
|
262
|
+
# property with this name set, simply does nothing.
|
263
|
+
#
|
264
|
+
def delete(name)
|
265
|
+
name = name.to_s if name.kind_of? Symbol
|
266
|
+
Datastore.convert_exceptions do
|
267
|
+
removeProperty(name)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Returns true if a property has been set. This function can
|
272
|
+
# be used to test if a property has been specifically set
|
273
|
+
# to nil.
|
274
|
+
#
|
275
|
+
def has_property?(name)
|
276
|
+
name = name.to_s if name.kind_of? Symbol
|
277
|
+
Datastore.convert_exceptions do
|
278
|
+
hasProperty(name)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
alias :has_property :has_property?
|
282
|
+
|
283
|
+
# Add the properties from +other+ to this Entity.
|
284
|
+
# Other may be an Entity or Hash
|
285
|
+
def update(other)
|
286
|
+
hash.each do |name, value|
|
287
|
+
self[name] = value
|
288
|
+
end
|
289
|
+
self
|
290
|
+
end
|
291
|
+
alias merge! update
|
292
|
+
|
293
|
+
# Iterates over all the properties in this Entity.
|
294
|
+
def each(&proc) # :yields: name, value
|
295
|
+
getProperties.each(&proc)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
SPECIAL_RUBY_TYPES = [Time, Text, Blob, ByteString, Link].freeze
|
300
|
+
|
301
|
+
def Datastore.ruby_to_java(value) # :nodoc:
|
302
|
+
if SPECIAL_RUBY_TYPES.include? value.class
|
303
|
+
value.to_java
|
304
|
+
else
|
305
|
+
case value
|
306
|
+
when Fixnum
|
307
|
+
java.lang.Long.new(value)
|
308
|
+
when Float
|
309
|
+
java.lang.Double.new(value)
|
310
|
+
when String
|
311
|
+
value.to_java_string
|
312
|
+
else
|
313
|
+
value
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def Datastore.convert_exceptions # :nodoc:
|
319
|
+
begin
|
320
|
+
yield
|
321
|
+
rescue java.lang.IllegalArgumentException => ex
|
322
|
+
raise ArgumentError, ex.message
|
323
|
+
rescue java.util.ConcurrentModificationException => ex
|
324
|
+
raise TransactionFailed, ex.message
|
325
|
+
rescue java.util.NoSuchElementException => ex
|
326
|
+
raise IndexError, ex.message
|
327
|
+
rescue JavaDatastore::DatastoreNeedIndexException => ex
|
328
|
+
raise NeedIndex, ex.message
|
329
|
+
rescue JavaDatastore::DatastoreTimeoutException => ex
|
330
|
+
raise Timeout, ex.message
|
331
|
+
rescue JavaDatastore::DatastoreFailureException => ex
|
332
|
+
raise InternalError, ex.message
|
333
|
+
rescue JavaDatastore::EntityNotFoundException => ex
|
334
|
+
raise EnityNotFound, ex.message
|
335
|
+
rescue JavaDatastore::PreparedQuery::TooManyResultsException => ex
|
336
|
+
raise TooManyResults, ex.message
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
@@ -0,0 +1,69 @@
|
|
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
|
+
# Replacement for the standard logger.rb which uses the Google App Engine
|
20
|
+
# logging API.
|
21
|
+
|
22
|
+
require 'appengine-apis/apiproxy'
|
23
|
+
require 'logger'
|
24
|
+
|
25
|
+
module AppEngine
|
26
|
+
class Logger < ::Logger
|
27
|
+
SEVERITIES = {
|
28
|
+
DEBUG => ApiProxy::LogRecord::Level::debug,
|
29
|
+
INFO => ApiProxy::LogRecord::Level::info,
|
30
|
+
WARN => ApiProxy::LogRecord::Level::warn,
|
31
|
+
ERROR => ApiProxy::LogRecord::Level::error,
|
32
|
+
FATAL => ApiProxy::LogRecord::Level::fatal,
|
33
|
+
}
|
34
|
+
SEVERITIES.default = ApiProxy::LogRecord::Level::info
|
35
|
+
|
36
|
+
def initialize(*args)
|
37
|
+
super(STDERR)
|
38
|
+
end
|
39
|
+
|
40
|
+
def <<(msg)
|
41
|
+
write_log(INFO, msg.to_s, "")
|
42
|
+
end
|
43
|
+
|
44
|
+
def add(severity, msg=nil, progname=nil, &block)
|
45
|
+
severity ||= UNKNOWN
|
46
|
+
return if severity < @level
|
47
|
+
progname ||= @progname
|
48
|
+
if msg.nil?
|
49
|
+
if block_given?
|
50
|
+
msg = yield
|
51
|
+
else
|
52
|
+
msg = progname
|
53
|
+
progname = @progname
|
54
|
+
end
|
55
|
+
end
|
56
|
+
write_log(severity, msg, progname)
|
57
|
+
end
|
58
|
+
alias log add
|
59
|
+
|
60
|
+
private
|
61
|
+
def write_log(severity, msg, progname)
|
62
|
+
level = SEVERITIES[severity]
|
63
|
+
if progname && !progname.empty? && progname != msg
|
64
|
+
msg = "#{progname}: #{msg}"
|
65
|
+
end
|
66
|
+
ApiProxy.log(level, msg)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,55 @@
|
|
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
|
+
# Switches the Merb Logger class to use the Google App Engine logging API.
|
20
|
+
|
21
|
+
require 'merb-core/logger'
|
22
|
+
|
23
|
+
module Merb
|
24
|
+
class Logger
|
25
|
+
def <<(string = nil)
|
26
|
+
AppEngine::ApiProxy.log(
|
27
|
+
AppEngine::ApiProxy::LogRecord::Level::info, string)
|
28
|
+
end
|
29
|
+
alias :push :<<
|
30
|
+
|
31
|
+
# Re-generate the logging methods for Merb.logger for each log level.
|
32
|
+
Levels.each_pair do |name, number|
|
33
|
+
class_eval <<-LEVELMETHODS, __FILE__, __LINE__
|
34
|
+
|
35
|
+
# Appends a message to the log if the log level is at least as high as
|
36
|
+
# the log level of the logger.
|
37
|
+
#
|
38
|
+
# ==== Parameters
|
39
|
+
# string<String>:: The message to be logged. Defaults to nil.
|
40
|
+
#
|
41
|
+
# ==== Returns
|
42
|
+
# self:: The logger object for chaining.
|
43
|
+
def #{name}(message = nil)
|
44
|
+
if #{number} >= level
|
45
|
+
message = block_given? ? yield : message
|
46
|
+
AppEngine::ApiProxy.log(
|
47
|
+
AppEngine::ApiProxy::LogRecord::Level::#{name}, message)
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
51
|
+
alias :#{name}! :#{name}
|
52
|
+
LEVELMETHODS
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|