cfrederick-titanium 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.rdoc +5 -0
- data/lib/base.rb +49 -0
- data/lib/configuration.rb +59 -0
- data/lib/resource.rb +144 -0
- data/lib/server.rb +262 -0
- data/lib/sqlite_adapter.rb +337 -0
- data/lib/titanium.rb +30 -0
- metadata +96 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2009 Cody Frederick
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
data/lib/base.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Copyright (c) 2009 Cody Frederick
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Titanium
|
22
|
+
|
23
|
+
class Base
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def log_error(progname=nil, &block)
|
28
|
+
Titanium::Configuration.logger.error(progname, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def log_warn(progname=nil, &block)
|
32
|
+
Titanium::Configuration.logger.warn(progname, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def log_info(progname=nil, &block)
|
36
|
+
Titanium::Configuration.logger.info(progname, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def log_debug(progname=nil, &block)
|
40
|
+
Titanium::Configuration.logger.debug(progname, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def environment
|
44
|
+
Titanium::Configuration.environment
|
45
|
+
end
|
46
|
+
|
47
|
+
end # Base
|
48
|
+
|
49
|
+
end # Titanium
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Copyright (c) 2009 Cody Frederick
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Titanium
|
22
|
+
|
23
|
+
module Configuration
|
24
|
+
|
25
|
+
def self.environment
|
26
|
+
@environment ||= ENV['RACK_ENV'] || 'development'
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.environment=(value)
|
30
|
+
@environment = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.logger
|
34
|
+
@logger ||= Logger.new(STDOUT)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.logger=(value)
|
38
|
+
@logger = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.clean_start
|
42
|
+
@clean_start ||= false
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.clean_start=(value)
|
46
|
+
@clean_start = value
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.db_adapter
|
50
|
+
@db_adapter ||= SQLiteAdapter
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.db_adapter=(value)
|
54
|
+
@db_adapter = value
|
55
|
+
end
|
56
|
+
|
57
|
+
end # Configuration
|
58
|
+
|
59
|
+
end # Titanium
|
data/lib/resource.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
# Copyright (c) 2009 Cody Frederick
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Titanium
|
22
|
+
|
23
|
+
class Resource < Base
|
24
|
+
attr_reader :name, :key
|
25
|
+
attr_accessor :metadata
|
26
|
+
|
27
|
+
# Constructs a new Resource. Additional options can be provided as shown below.
|
28
|
+
# r = Resource.new("book")
|
29
|
+
# r = Resource.new("book", {:metadata => {:title => "Animal Farm"}})
|
30
|
+
# r = Resource.new("book", {:key => "128dfrit12wywbc", :metadata => {:title => "Animal Farm"}})
|
31
|
+
def initialize(name, options={})
|
32
|
+
@name = name
|
33
|
+
@key = options[:key]
|
34
|
+
@metadata = options[:metadata]
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_json
|
38
|
+
json_hash = @metadata.dup
|
39
|
+
json_hash[:key] = @key
|
40
|
+
json_hash.to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
def insert
|
44
|
+
db_instance = Configuration.db_adapter.new
|
45
|
+
begin
|
46
|
+
db_instance.connect
|
47
|
+
return db_instance.insert(@name, @metadata)
|
48
|
+
ensure
|
49
|
+
db_instance.disconnect
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def update
|
54
|
+
db_instance = Configuration.db_adapter.new
|
55
|
+
begin
|
56
|
+
db_instance.connect
|
57
|
+
return db_instance.update(@key, @metadata)
|
58
|
+
ensure
|
59
|
+
db_instance.disconnect
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def delete
|
64
|
+
db_instance = Configuration.db_adapter.new
|
65
|
+
begin
|
66
|
+
db_instance.connect
|
67
|
+
return db_instance.delete(@key)
|
68
|
+
ensure
|
69
|
+
db_instance.disconnect
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.find_by_name(name, sort_field=nil, sort_order=nil, limit=nil, offset=nil)
|
74
|
+
db_instance = Configuration.db_adapter.new
|
75
|
+
begin
|
76
|
+
db_instance.connect
|
77
|
+
resources = []
|
78
|
+
result = db_instance.find_by_name_and_conditions(name, nil, nil, sort_field, sort_order, limit, offset)
|
79
|
+
result.each do |row|
|
80
|
+
key = row.delete('key')
|
81
|
+
resources << Resource.new(name, {:key => key, :metadata => row})
|
82
|
+
end
|
83
|
+
return resources
|
84
|
+
ensure
|
85
|
+
db_instance.disconnect
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.count_by_name(name)
|
90
|
+
db_instance = Configuration.db_adapter.new
|
91
|
+
begin
|
92
|
+
db_instance.connect
|
93
|
+
count = db_instance.count_by_name(name)
|
94
|
+
return count
|
95
|
+
ensure
|
96
|
+
db_instance.disconnect
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.delete_all_by_name(name)
|
101
|
+
db_instance = Configuration.db_adapter.new
|
102
|
+
begin
|
103
|
+
db_instance.connect
|
104
|
+
return db_instance.delete_all_by_name(name)
|
105
|
+
ensure
|
106
|
+
db_instance.disconnect
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.find_by_name_and_key(name, key)
|
111
|
+
db_instance = Configuration.db_adapter.new
|
112
|
+
begin
|
113
|
+
db_instance.connect
|
114
|
+
resources = []
|
115
|
+
result = db_instance.find_by_name_and_conditions(name, key)
|
116
|
+
result.each do |row|
|
117
|
+
key = row.delete('key')
|
118
|
+
resources << Resource.new(name, {:key => key, :metadata => row})
|
119
|
+
end
|
120
|
+
return resources
|
121
|
+
ensure
|
122
|
+
db_instance.disconnect
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.find_by_name_and_params(name, params, sort_field=nil, sort_order=nil, limit=nil, offset=nil)
|
127
|
+
db_instance = Configuration.db_adapter.new
|
128
|
+
begin
|
129
|
+
db_instance.connect
|
130
|
+
resources = []
|
131
|
+
result = db_instance.find_by_name_and_conditions(name, nil, params, sort_field, sort_order, limit, offset)
|
132
|
+
result.each do |row|
|
133
|
+
key = row.delete('key')
|
134
|
+
resources << Resource.new(name, {:key => key, :metadata => row})
|
135
|
+
end
|
136
|
+
return resources
|
137
|
+
ensure
|
138
|
+
db_instance.disconnect
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end # Resource
|
143
|
+
|
144
|
+
end # Titanium
|
data/lib/server.rb
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
# Copyright (c) 2009 Cody Frederick
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'rubygems'
|
22
|
+
require 'rack'
|
23
|
+
require 'json/pure'
|
24
|
+
|
25
|
+
module Titanium
|
26
|
+
|
27
|
+
class Server < Base
|
28
|
+
include Rack::Utils
|
29
|
+
|
30
|
+
# Constructs a new Application. This effectively starts the entire web server process which also includes
|
31
|
+
# initializing all other downstream processes such as the database layer.
|
32
|
+
def initialize(root)
|
33
|
+
@app_root = root
|
34
|
+
@file_server = Rack::File.new(@app_root)
|
35
|
+
erase_db if Configuration.clean_start
|
36
|
+
start_db
|
37
|
+
end
|
38
|
+
|
39
|
+
def erase_db
|
40
|
+
db_instance = Configuration.db_adapter.new
|
41
|
+
begin
|
42
|
+
db_instance.erase_db
|
43
|
+
rescue Exception => ex
|
44
|
+
log_error(ex)
|
45
|
+
return nil
|
46
|
+
ensure
|
47
|
+
db_instance.disconnect
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def start_db
|
52
|
+
db_instance = Configuration.db_adapter.new
|
53
|
+
begin
|
54
|
+
db_instance.connect
|
55
|
+
db_instance.startup
|
56
|
+
rescue Exception => ex
|
57
|
+
log_error(ex)
|
58
|
+
return nil
|
59
|
+
ensure
|
60
|
+
db_instance.disconnect
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Handles the Rack request.
|
65
|
+
def call(env)
|
66
|
+
begin
|
67
|
+
create_rack_request(env)
|
68
|
+
if is_valid_request?
|
69
|
+
if is_static_request?
|
70
|
+
serve_static_request
|
71
|
+
else
|
72
|
+
serve_dynamic_request
|
73
|
+
end
|
74
|
+
else
|
75
|
+
serve_bad_request
|
76
|
+
end
|
77
|
+
rescue Exception => ex
|
78
|
+
log_error(ex)
|
79
|
+
serve_internal_server_error
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
# Constructs a new Rack::Request. This also updates the path_info to the index.html page if no resource is
|
86
|
+
# requested.
|
87
|
+
def create_rack_request(env)
|
88
|
+
@request = Rack::Request.new(env)
|
89
|
+
@request.path_info = '/index.html' if @request.path_info == '/'
|
90
|
+
end
|
91
|
+
|
92
|
+
# Determines if the request is valid.
|
93
|
+
# * Checks the http method verb
|
94
|
+
def is_valid_request?
|
95
|
+
is_valid_method?
|
96
|
+
end
|
97
|
+
|
98
|
+
# Determines if the http request method is acceptable. Only GET, POST, PUT, and DELETE are acceptable.
|
99
|
+
def is_valid_method?
|
100
|
+
case @request.request_method
|
101
|
+
when 'GET', 'POST', 'PUT', 'DELETE' then true
|
102
|
+
else false
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Determines if the request is for a static resource. All static requests must be for a resource with a file
|
107
|
+
# extension
|
108
|
+
def is_static_request?
|
109
|
+
get_resource_from_request =~ /\..*$/
|
110
|
+
end
|
111
|
+
|
112
|
+
# Forms a static file path from the request's path information.
|
113
|
+
def get_static_path_from_request
|
114
|
+
"#{@app_root}#{@request.path_info}"
|
115
|
+
end
|
116
|
+
|
117
|
+
# Serves a 400 status code.
|
118
|
+
def serve_bad_request
|
119
|
+
serve_error_code_response(400, "Bad Request")
|
120
|
+
end
|
121
|
+
|
122
|
+
# Serves a 404 status code.
|
123
|
+
def serve_not_found
|
124
|
+
serve_error_code_response(404, "Not Found")
|
125
|
+
end
|
126
|
+
|
127
|
+
# Serves a 500 status code.
|
128
|
+
def serve_internal_server_error
|
129
|
+
serve_error_code_response(500, "Internal Server Error")
|
130
|
+
end
|
131
|
+
|
132
|
+
# Serves an error page (or default message if the page cannot be found) for a given response code
|
133
|
+
def serve_error_code_response(code, message)
|
134
|
+
if File.exist?("#{@app_root}/#{code}.html")
|
135
|
+
@request.path_info = "/#{code}.html"
|
136
|
+
@file_server.call(@request.env)
|
137
|
+
else
|
138
|
+
[code, { 'Content-Type' => 'text/plain' }, message]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Extracts the resource requested. The resource equals the entire path info minus the query string
|
143
|
+
def get_resource_from_request
|
144
|
+
@request.path_info.split("@").first
|
145
|
+
end
|
146
|
+
|
147
|
+
# Extracts the key of the resource requested.
|
148
|
+
# /application/model/book => nil
|
149
|
+
# /library/book@4 => 4
|
150
|
+
def get_key_from_request
|
151
|
+
@request.path_info.include?("@") ? @request.path_info.split("@").last : nil
|
152
|
+
end
|
153
|
+
|
154
|
+
# Serves a static request.
|
155
|
+
def serve_static_request
|
156
|
+
if File.exist?(get_static_path_from_request)
|
157
|
+
@file_server.call(@request.env)
|
158
|
+
else
|
159
|
+
serve_not_found
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Serves a dynamic request.
|
164
|
+
def serve_dynamic_request
|
165
|
+
response_hash = self.send(@request.request_method.strip.downcase)
|
166
|
+
@response = Rack::Response.new
|
167
|
+
@response.headers['Content-Type'] = "application/json"
|
168
|
+
@response.write(response_hash.to_json)
|
169
|
+
@response.finish
|
170
|
+
end
|
171
|
+
|
172
|
+
# Updates a resource in the datastore. Called from serve_dynamic_request.
|
173
|
+
def put
|
174
|
+
resource_name = get_resource_from_request
|
175
|
+
resource_key = get_key_from_request
|
176
|
+
metadata = parse_query(@request.body.read)
|
177
|
+
if resource_key
|
178
|
+
resources = Resource.find_by_name_and_key(resource_name, resource_key)
|
179
|
+
if resources && resources.first
|
180
|
+
resource = resources.first
|
181
|
+
resource.metadata = metadata
|
182
|
+
key = resource.update
|
183
|
+
if key
|
184
|
+
{:status => :success, :key => key}
|
185
|
+
else
|
186
|
+
{:status => :failed, :key => nil}
|
187
|
+
end
|
188
|
+
else
|
189
|
+
{:status => :failed, :message => "Unable to find resource with the key: #{resource_key}"}
|
190
|
+
end
|
191
|
+
else
|
192
|
+
serve_bad_request
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Retreives a list of resources. Called from serve_dynamic_request.
|
197
|
+
def get
|
198
|
+
resource_name = get_resource_from_request
|
199
|
+
resource_key = get_key_from_request
|
200
|
+
params = @request.GET
|
201
|
+
sort_field = params.delete('sort-field')
|
202
|
+
sort_order = params.delete('sort-order')
|
203
|
+
limit = params.delete('limit')
|
204
|
+
offset = params.delete('offset')
|
205
|
+
if resource_key
|
206
|
+
if resource_key == "_count"
|
207
|
+
count = Resource.count_by_name(resource_name)
|
208
|
+
else
|
209
|
+
resources = Resource.find_by_name_and_key(resource_name, resource_key)
|
210
|
+
end
|
211
|
+
else
|
212
|
+
if params.nil? or params.empty?
|
213
|
+
resources = Resource.find_by_name(resource_name, sort_field, sort_order, limit, offset)
|
214
|
+
else
|
215
|
+
resources = Resource.find_by_name_and_params(resource_name, params, sort_field, sort_order, limit, offset)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
{:status => :success, :entities => resources, :count => count || resources.length}
|
219
|
+
end
|
220
|
+
|
221
|
+
# Inserts the resource into the datastore. Called from serve_dynamic_request.
|
222
|
+
def post
|
223
|
+
resource_name = get_resource_from_request
|
224
|
+
metadata = @request.POST
|
225
|
+
metadata.delete("key")
|
226
|
+
resource = Resource.new(resource_name, {:metadata => metadata})
|
227
|
+
key = resource.insert
|
228
|
+
if key
|
229
|
+
{:status => :success, :key => key}
|
230
|
+
else
|
231
|
+
{:status => :failed, :key => nil}
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Deletes a resource in the datastore. Called from serve_dynamic_request.
|
236
|
+
def delete
|
237
|
+
resource_name = get_resource_from_request
|
238
|
+
resource_key = get_key_from_request
|
239
|
+
if resource_key
|
240
|
+
if resource_key == "_all"
|
241
|
+
if Resource.delete_all_by_name(resource_name)
|
242
|
+
{:status => :success}
|
243
|
+
else
|
244
|
+
{:status => :failed, :message => "Failed to delete all entities of type: #{resource_name}"}
|
245
|
+
end
|
246
|
+
else
|
247
|
+
resources = Resource.find_by_name_and_key(resource_name, resource_key)
|
248
|
+
if resources && resources.first
|
249
|
+
resource = resources.first
|
250
|
+
resource.delete ? {:status => :success} : {:status => :failed}
|
251
|
+
else
|
252
|
+
{:status => :failed, :message => "Unable to find resource with the key: #{resource_key}"}
|
253
|
+
end
|
254
|
+
end
|
255
|
+
else
|
256
|
+
serve_bad_request
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
end # Server
|
261
|
+
|
262
|
+
end # Titanium
|
@@ -0,0 +1,337 @@
|
|
1
|
+
# Copyright (c) 2009 Cody Frederick
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'rubygems'
|
22
|
+
require 'time'
|
23
|
+
require 'sqlite3'
|
24
|
+
|
25
|
+
include SQLite3
|
26
|
+
|
27
|
+
module Titanium
|
28
|
+
|
29
|
+
class SQLiteAdapter < Base
|
30
|
+
attr_reader :db_file
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@db_file = "data/#{environment}.db"
|
34
|
+
end
|
35
|
+
|
36
|
+
def erase_db
|
37
|
+
File.delete(File.expand_path(@db_file))
|
38
|
+
end
|
39
|
+
|
40
|
+
def connect
|
41
|
+
@db = Database.new(@db_file)
|
42
|
+
@db.results_as_hash = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def disconnect
|
46
|
+
if @db
|
47
|
+
@db.close unless @db.closed?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def startup
|
52
|
+
@db.transaction do
|
53
|
+
@db.execute(sql_create_types_table)
|
54
|
+
@db.execute(sql_create_resources_table)
|
55
|
+
@db.execute(sql_create_attributes_table)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def insert(name, metadata)
|
60
|
+
resource_key = nil
|
61
|
+
@db.transaction do
|
62
|
+
type_id = @db.get_first_value(sql_find_type_id_from_type_name, :name => name)
|
63
|
+
if type_id.nil?
|
64
|
+
@db.execute(sql_insert_type, :name => name)
|
65
|
+
type_id = @db.last_insert_row_id
|
66
|
+
end
|
67
|
+
@db.execute(sql_insert_resource, :type_id => type_id)
|
68
|
+
resource_key = @db.last_insert_row_id
|
69
|
+
now = Time.now.iso8601
|
70
|
+
metadata['created_at'] = now
|
71
|
+
metadata['updated_at'] = now
|
72
|
+
metadata.each do |key,value|
|
73
|
+
@db.execute(sql_insert_attribute, :resource_key => resource_key, :key => key, :value => value)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
return resource_key
|
77
|
+
end
|
78
|
+
|
79
|
+
def update(resource_key, metadata)
|
80
|
+
@db.transaction do
|
81
|
+
@db.execute(sql_delete_attributes_by_resource_key, :resource_key => resource_key)
|
82
|
+
metadata['created_at'] ||= Time.now.iso8601
|
83
|
+
metadata['updated_at'] = Time.now.iso8601
|
84
|
+
metadata.each do |key,value|
|
85
|
+
@db.execute(sql_insert_attribute, :resource_key => resource_key, :key => key, :value => value)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
return resource_key
|
89
|
+
end
|
90
|
+
|
91
|
+
def delete(resource_key)
|
92
|
+
@db.transaction do
|
93
|
+
resources_remaining = @db.get_first_value(sql_count_of_resources_by_resource_key, :resource_key => resource_key)
|
94
|
+
@db.execute(sql_delete_attributes_by_resource_key, :resource_key => resource_key)
|
95
|
+
@db.execute(sql_delete_type_by_resource_key, :resource_key => resource_key) if resources_remaining.to_i == 1
|
96
|
+
@db.execute(sql_delete_resource_by_resource_key, :resource_key => resource_key)
|
97
|
+
end
|
98
|
+
return true
|
99
|
+
end
|
100
|
+
|
101
|
+
def count_by_name(name)
|
102
|
+
return @db.get_first_value(sql_count_of_resources_by_type_name, :name => name)
|
103
|
+
end
|
104
|
+
|
105
|
+
def delete_all_by_name(name)
|
106
|
+
@db.transaction do
|
107
|
+
@db.execute_batch(sql_delete_resources_by_type_name, :name => name)
|
108
|
+
end
|
109
|
+
return true
|
110
|
+
end
|
111
|
+
|
112
|
+
def find_by_name_and_conditions(name, key=nil, conditions=nil, sort_field=nil, sort_order=nil, limit=nil, offset=nil)
|
113
|
+
results = {}
|
114
|
+
query_id = generate_query_id
|
115
|
+
|
116
|
+
@db.transaction do
|
117
|
+
index = 0
|
118
|
+
@db.execute(sql_drop_sorter_table(query_id))
|
119
|
+
@db.execute(sql_create_sorter_table(query_id, name, key, conditions, sort_field, sort_order, limit, offset))
|
120
|
+
@db.execute(sql_query_sorter_table(query_id)) do |row|
|
121
|
+
build_results_hash(results, index, row)
|
122
|
+
index = index + 1
|
123
|
+
end
|
124
|
+
@db.execute(sql_drop_sorter_table(query_id))
|
125
|
+
end
|
126
|
+
|
127
|
+
return build_results_array(results)
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def generate_query_id
|
133
|
+
values = [rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x1000000), rand(0x1000000)]
|
134
|
+
"%04x%04x%04x%04x%04x%06x%06x" % values
|
135
|
+
end
|
136
|
+
|
137
|
+
def sql_drop_sorter_table(query_id)
|
138
|
+
"DROP TABLE IF EXISTS tmp_#{query_id}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def sql_create_sorter_table(query_id, name, key=nil, conditions=nil, sort_field=nil, sort_order=nil, limit=nil, offset=nil)
|
142
|
+
# resolving variables below instead of in the argument signature because there is a difference between
|
143
|
+
# not supplying the argument and providing a nil value. this adapter relies on resolving nil values
|
144
|
+
# for the arguments in the signature.
|
145
|
+
key = key ? "AND r.id = '#{key}'" : ""
|
146
|
+
sort_field ||="created_at"
|
147
|
+
sort_order ||= "ASC"
|
148
|
+
limit ||= "-1"
|
149
|
+
offset ||="0"
|
150
|
+
sql = ""
|
151
|
+
|
152
|
+
if conditions
|
153
|
+
sql = "%subquery%"
|
154
|
+
conditions.each_pair do |attr_key,attr_value|
|
155
|
+
conditions_sql = <<-SQL
|
156
|
+
SELECT DISTINCT r.id
|
157
|
+
FROM resources r
|
158
|
+
INNER JOIN attributes a ON a.resource_id = r.id
|
159
|
+
WHERE a.key = '#{attr_key}' AND a.value = '#{attr_value}'
|
160
|
+
AND r.id IN (
|
161
|
+
%subquery%
|
162
|
+
)
|
163
|
+
SQL
|
164
|
+
sql.gsub!("%subquery%", conditions_sql)
|
165
|
+
end
|
166
|
+
typesql = <<-SQL
|
167
|
+
SELECT r.id
|
168
|
+
FROM resources r
|
169
|
+
INNER JOIN types t ON t.id = r.type_id
|
170
|
+
WHERE t.name = '#{name}'
|
171
|
+
#{key}
|
172
|
+
SQL
|
173
|
+
sql.gsub!("%subquery%", typesql)
|
174
|
+
else
|
175
|
+
sql = <<-SQL
|
176
|
+
SELECT r.id
|
177
|
+
FROM resources r
|
178
|
+
INNER JOIN types t ON t.id = r.type_id
|
179
|
+
WHERE t.name = '#{name}'
|
180
|
+
#{key}
|
181
|
+
SQL
|
182
|
+
end
|
183
|
+
|
184
|
+
sorter_table_sql = <<-SQL
|
185
|
+
CREATE TEMP TABLE tmp_#{query_id} AS
|
186
|
+
SELECT r.id as resource_id, a.value
|
187
|
+
FROM resources r
|
188
|
+
INNER JOIN attributes a ON a.resource_id = r.id
|
189
|
+
WHERE a.key = '#{sort_field}'
|
190
|
+
AND r.id IN (
|
191
|
+
%subquery%
|
192
|
+
)
|
193
|
+
ORDER BY a.value #{sort_order}
|
194
|
+
LIMIT #{limit}
|
195
|
+
OFFSET #{offset}
|
196
|
+
SQL
|
197
|
+
sorter_table_sql.gsub!("%subquery%", sql)
|
198
|
+
|
199
|
+
return sorter_table_sql
|
200
|
+
end
|
201
|
+
|
202
|
+
def sql_query_sorter_table(query_id)
|
203
|
+
<<-SQL
|
204
|
+
SELECT
|
205
|
+
r.id AS key,
|
206
|
+
a.key AS attr_key,
|
207
|
+
a.value AS attr_value
|
208
|
+
FROM resources r
|
209
|
+
INNER JOIN attributes a ON a.resource_id = r.id
|
210
|
+
INNER JOIN tmp_#{query_id} s ON s.resource_id = r.id
|
211
|
+
ORDER BY s.ROWID ASC;
|
212
|
+
SQL
|
213
|
+
end
|
214
|
+
|
215
|
+
def build_results_hash(results, index, row)
|
216
|
+
key = row['key']
|
217
|
+
attr_key = row['attr_key']
|
218
|
+
attr_value = row['attr_value']
|
219
|
+
results[key] ||= {}
|
220
|
+
results[key]['query_index'] ||= index
|
221
|
+
results[key]['key'] ||= key
|
222
|
+
results[key][attr_key] ||= attr_value
|
223
|
+
end
|
224
|
+
|
225
|
+
def build_results_array(results)
|
226
|
+
sorted_results = results.sort { |a,b| a[1]['query_index'] <=> b[1]['query_index'] }
|
227
|
+
results_array = []
|
228
|
+
sorted_results.each do |item|
|
229
|
+
item[1].delete('query_index')
|
230
|
+
results_array << item[1]
|
231
|
+
end
|
232
|
+
return results_array
|
233
|
+
end
|
234
|
+
|
235
|
+
def sql_create_types_table
|
236
|
+
<<-SQL
|
237
|
+
CREATE TABLE IF NOT EXISTS types (
|
238
|
+
id INTEGER PRIMARY KEY NOT NULL,
|
239
|
+
name TEXT NOT NULL
|
240
|
+
);
|
241
|
+
SQL
|
242
|
+
end
|
243
|
+
|
244
|
+
def sql_create_resources_table
|
245
|
+
<<-SQL
|
246
|
+
CREATE TABLE IF NOT EXISTS resources (
|
247
|
+
id INTEGER PRIMARY KEY NOT NULL,
|
248
|
+
type_id INTEGER REFERENCES types(id)
|
249
|
+
);
|
250
|
+
SQL
|
251
|
+
end
|
252
|
+
|
253
|
+
def sql_create_attributes_table
|
254
|
+
<<-SQL
|
255
|
+
CREATE TABLE IF NOT EXISTS attributes (
|
256
|
+
id INTEGER PRIMARY KEY NOT NULL,
|
257
|
+
resource_id INTEGER REFERENCES resources(id),
|
258
|
+
key TEXT NOT NULL,
|
259
|
+
value TEXT
|
260
|
+
);
|
261
|
+
SQL
|
262
|
+
end
|
263
|
+
|
264
|
+
def sql_insert_type
|
265
|
+
"INSERT INTO types (name) VALUES (:name)"
|
266
|
+
end
|
267
|
+
|
268
|
+
def sql_insert_resource
|
269
|
+
"INSERT INTO resources (type_id) VALUES (:type_id)"
|
270
|
+
end
|
271
|
+
|
272
|
+
def sql_insert_attribute
|
273
|
+
"INSERT INTO attributes (resource_id, key, value) VALUES (:resource_key, :key, :value)"
|
274
|
+
end
|
275
|
+
|
276
|
+
def sql_delete_attributes_by_resource_key
|
277
|
+
"DELETE FROM attributes WHERE resource_id = :resource_key"
|
278
|
+
end
|
279
|
+
|
280
|
+
def sql_delete_resource_by_resource_key
|
281
|
+
"DELETE FROM resources WHERE id = :resource_key"
|
282
|
+
end
|
283
|
+
|
284
|
+
def sql_delete_type_by_resource_key
|
285
|
+
<<-SQL
|
286
|
+
DELETE FROM types
|
287
|
+
WHERE id IN (
|
288
|
+
SELECT type_id FROM resources WHERE id = :resource_key
|
289
|
+
)
|
290
|
+
SQL
|
291
|
+
end
|
292
|
+
|
293
|
+
def sql_count_of_resources_by_resource_key
|
294
|
+
<<-SQL
|
295
|
+
SELECT COUNT(*)
|
296
|
+
FROM resources
|
297
|
+
WHERE type_id IN (
|
298
|
+
SELECT type_id FROM resources WHERE id = :resource_key
|
299
|
+
)
|
300
|
+
SQL
|
301
|
+
end
|
302
|
+
|
303
|
+
def sql_count_of_resources_by_type_name
|
304
|
+
<<-SQL
|
305
|
+
SELECT COUNT(*)
|
306
|
+
FROM resources r
|
307
|
+
INNER JOIN types t ON t.id = r.type_id
|
308
|
+
WHERE t.name = :name
|
309
|
+
SQL
|
310
|
+
end
|
311
|
+
|
312
|
+
def sql_delete_resources_by_type_name
|
313
|
+
<<-SQL
|
314
|
+
DELETE FROM attributes
|
315
|
+
WHERE resource_id IN (
|
316
|
+
SELECT r.id
|
317
|
+
FROM resources r
|
318
|
+
INNER JOIN types t ON t.id = r.type_id
|
319
|
+
WHERE t.name = :name
|
320
|
+
);
|
321
|
+
|
322
|
+
DELETE FROM resources
|
323
|
+
WHERE type_id IN (
|
324
|
+
SELECT id FROM types WHERE name = :name
|
325
|
+
);
|
326
|
+
|
327
|
+
DELETE FROM types WHERE name = :name;
|
328
|
+
SQL
|
329
|
+
end
|
330
|
+
|
331
|
+
def sql_find_type_id_from_type_name
|
332
|
+
"SELECT id FROM types WHERE name = :name"
|
333
|
+
end
|
334
|
+
|
335
|
+
end # SQLiteAdapter
|
336
|
+
|
337
|
+
end # Titanium
|
data/lib/titanium.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Copyright (c) 2009 Cody Frederick
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'logger'
|
22
|
+
require 'base'
|
23
|
+
require 'configuration'
|
24
|
+
require 'server'
|
25
|
+
require 'resource'
|
26
|
+
require 'sqlite_adapter'
|
27
|
+
|
28
|
+
module Titanium
|
29
|
+
VERSION = "0.5.1"
|
30
|
+
end # Titanium
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cfrederick-titanium
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cody Frederick
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-05-16 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "1.0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: sqlite3-ruby
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.2.4
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: json_pure
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.1.7
|
44
|
+
version:
|
45
|
+
description: Lightweight but powerful RESTful application server
|
46
|
+
email: cody@codingsouthpaw.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- README.rdoc
|
53
|
+
- LICENSE
|
54
|
+
files:
|
55
|
+
- README.rdoc
|
56
|
+
- lib/base.rb
|
57
|
+
- lib/configuration.rb
|
58
|
+
- lib/resource.rb
|
59
|
+
- lib/server.rb
|
60
|
+
- lib/sqlite_adapter.rb
|
61
|
+
- lib/titanium.rb
|
62
|
+
- LICENSE
|
63
|
+
has_rdoc: true
|
64
|
+
homepage: https://github.com/cfrederick/titanium
|
65
|
+
licenses:
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options:
|
68
|
+
- --line-numbers
|
69
|
+
- --inline-source
|
70
|
+
- --title
|
71
|
+
- titanium
|
72
|
+
- --main
|
73
|
+
- README.rdoc
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: "0"
|
81
|
+
version:
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: "0"
|
87
|
+
version:
|
88
|
+
requirements: []
|
89
|
+
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.3.5
|
92
|
+
signing_key:
|
93
|
+
specification_version: 2
|
94
|
+
summary: Lightweight but powerful RESTful application server
|
95
|
+
test_files: []
|
96
|
+
|