riddl 0.99.105
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.
- data/AUTHORS +1 -0
- data/COPYING +165 -0
- data/INSTALL +24 -0
- data/README.rdoc +2 -0
- data/Rakefile +17 -0
- data/TODO +17 -0
- data/contrib/riddl.jpg +0 -0
- data/contrib/riddl.png +0 -0
- data/contrib/riddl.svg +138 -0
- data/lib/riddl/client.rb +423 -0
- data/lib/riddl/commonlogger.rb +16 -0
- data/lib/riddl/constants.rb +5 -0
- data/lib/riddl/error.rb +8 -0
- data/lib/riddl/handlers.rb +14 -0
- data/lib/riddl/handlers/oauth.rb +19 -0
- data/lib/riddl/handlers/plain-type.rb +21 -0
- data/lib/riddl/handlers/relaxng.rb +16 -0
- data/lib/riddl/handlers/xmlschema.rb +16 -0
- data/lib/riddl/header.rb +9 -0
- data/lib/riddl/implementation.rb +57 -0
- data/lib/riddl/ns/common-patterns/addon-security/request.xml +25 -0
- data/lib/riddl/ns/common-patterns/addon-security/response.xml +25 -0
- data/lib/riddl/ns/common-patterns/downloadify/1.0/downloadify.xml +18 -0
- data/lib/riddl/ns/common-patterns/notifications-consumer/1.0/consumer.xml +100 -0
- data/lib/riddl/ns/common-patterns/notifications-producer/1.0/producer.xml +204 -0
- data/lib/riddl/ns/common-patterns/properties/1.0/properties.schema.schema +140 -0
- data/lib/riddl/ns/common-patterns/properties/1.0/properties.schema.xsl +89 -0
- data/lib/riddl/ns/common-patterns/properties/1.0/properties.xml +150 -0
- data/lib/riddl/ns/common/datatypes-1_0.rng +79 -0
- data/lib/riddl/ns/common/relaxng-modular.rng +330 -0
- data/lib/riddl/ns/common/relaxng.rng +10 -0
- data/lib/riddl/ns/declaration/1.0/declaration.rng +114 -0
- data/lib/riddl/ns/description/1.0/description.rng +302 -0
- data/lib/riddl/option.rb +9 -0
- data/lib/riddl/parameter.rb +54 -0
- data/lib/riddl/protocols/http/generator.rb +121 -0
- data/lib/riddl/protocols/http/parser.rb +199 -0
- data/lib/riddl/protocols/websocket.rb +103 -0
- data/lib/riddl/protocols/xmpp/generator.rb +176 -0
- data/lib/riddl/protocols/xmpp/parser.rb +118 -0
- data/lib/riddl/roles.rb +15 -0
- data/lib/riddl/roles/http%3A%2F%2Foauth.net%2F1.0%2Faccess_token.rb +30 -0
- data/lib/riddl/roles/http%3A%2F%2Foauth.net%2F1.0%2Fon_behalf.rb +22 -0
- data/lib/riddl/roles/http%3A%2F%2Foauth.net%2F1.0%2Frequest_token.rb +30 -0
- data/lib/riddl/roles/http%3A%2F%2Foauth.net%2F1.0/base.rb +67 -0
- data/lib/riddl/server.rb +519 -0
- data/lib/riddl/utils/description.rb +29 -0
- data/lib/riddl/utils/downloadify.rb +14 -0
- data/lib/riddl/utils/erbserve.rb +23 -0
- data/lib/riddl/utils/fileserve.rb +31 -0
- data/lib/riddl/utils/notifications_producer.rb +310 -0
- data/lib/riddl/utils/properties.rb +474 -0
- data/lib/riddl/utils/xsloverlay.rb +21 -0
- data/lib/riddl/wrapper.rb +280 -0
- data/lib/riddl/wrapper/declaration.rb +103 -0
- data/lib/riddl/wrapper/declaration/facade.rb +94 -0
- data/lib/riddl/wrapper/declaration/interface.rb +34 -0
- data/lib/riddl/wrapper/declaration/tile.rb +107 -0
- data/lib/riddl/wrapper/description.rb +69 -0
- data/lib/riddl/wrapper/description/access.rb +108 -0
- data/lib/riddl/wrapper/description/message_and_transformation.rb +131 -0
- data/lib/riddl/wrapper/description/resource.rb +271 -0
- data/lib/riddl/wrapper/layerchecker.rb +33 -0
- data/lib/riddl/wrapper/messageparser.rb +221 -0
- data/lib/riddl/wrapper/resourcechecker.rb +98 -0
- data/ns/common-patterns/addon-security/request.xml +25 -0
- data/ns/common-patterns/addon-security/response.xml +25 -0
- data/ns/common-patterns/downloadify/1.0/downloadify.xml +18 -0
- data/ns/common-patterns/notifications-consumer/1.0/consumer.xml +100 -0
- data/ns/common-patterns/notifications-producer/1.0/producer.xml +204 -0
- data/ns/common-patterns/properties/1.0/properties.schema.schema +140 -0
- data/ns/common-patterns/properties/1.0/properties.schema.xsl +89 -0
- data/ns/common-patterns/properties/1.0/properties.xml +150 -0
- data/ns/common/datatypes-1_0.rng +79 -0
- data/ns/common/relaxng-modular.rng +330 -0
- data/ns/common/relaxng.rng +10 -0
- data/ns/declaration/1.0/declaration.rng +114 -0
- data/ns/description/1.0/description.rng +302 -0
- data/riddl.gemspec +33 -0
- data/test/smartrunner.rb +48 -0
- data/test/tc_declaration-distributed.rb +79 -0
- data/test/tc_declaration-hybrid.rb +71 -0
- data/test/tc_declaration-local.rb +47 -0
- data/test/tc_helloworld.rb +17 -0
- data/test/tc_producer.rb +54 -0
- data/test/tc_properties.rb +72 -0
- data/tools/flash-policy-server.rb +12 -0
- data/tools/riddlcheck +36 -0
- data/tools/riddlcheck-1_0 +36 -0
- data/tools/riddlprocess +51 -0
- data/tools/riddlprocess-1_0 +51 -0
- metadata +291 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../client')
|
2
|
+
|
3
|
+
module Riddl
|
4
|
+
module Utils
|
5
|
+
module Description
|
6
|
+
|
7
|
+
class XML < Riddl::Implementation
|
8
|
+
def response
|
9
|
+
return Riddl::Parameter::Complex.new("riddl-description","text/xml",@a[0])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Call < Riddl::Implementation
|
14
|
+
def response
|
15
|
+
client = Riddl::Client.new(@a[3],@a[4])
|
16
|
+
|
17
|
+
path = client.resource "/" + @a[5]
|
18
|
+
@status, result = if @a[0].nil?
|
19
|
+
path.request @m => [ Riddl::Header.new("RIDDL_DECLARATION_RESOURCE", @a[2]), Riddl::Header.new("RIDDL-DECLARATION-PATH", @a[1]) ] + @h.map{|a,b| Riddl::Header.new(a,b)} + @p
|
20
|
+
else
|
21
|
+
path.request @m => [ Riddl::Header.new("RIDDL_DECLARATION_RESOURCE", @a[2]), Riddl::Header.new("RIDDL-DECLARATION-PATH", @a[1]) ] + @a[0].headers.map{|a,b| Riddl::Header.new(a,b)} + @a[0].response
|
22
|
+
end
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'mime/types'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
module Riddl
|
5
|
+
module Utils
|
6
|
+
class Downloadify < Riddl::Implementation
|
7
|
+
def response
|
8
|
+
mimetype = @p.find{|e|e.name == 'mimetype'}.value
|
9
|
+
content = @p.find{|e|e.name == 'content'}.value
|
10
|
+
Riddl::Parameter::Complex.new("content",mimetype,content,@r.last)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'mime/types'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module Riddl
|
5
|
+
module Utils
|
6
|
+
class ERBServe < Riddl::Implementation
|
7
|
+
def response
|
8
|
+
path = File.file?(@a[0]) ? @a[0] : "#{@a[0]}/#{@r[@match.length..-1].join('/')}".gsub(/\/+/,'/')
|
9
|
+
if File.directory?(path)
|
10
|
+
@status = 404
|
11
|
+
return []
|
12
|
+
end
|
13
|
+
if File.exists?(path)
|
14
|
+
__ERB_FILE__ = path
|
15
|
+
rval = ERB.new(File.read(path), 0, "%<>")
|
16
|
+
return Riddl::Parameter::Complex.new("data",MIME::Types.type_for(path).to_s,rval.result(binding))
|
17
|
+
end
|
18
|
+
@status = 404
|
19
|
+
[]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'mime/types'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
module Riddl
|
5
|
+
module Utils
|
6
|
+
class FileServe < Riddl::Implementation
|
7
|
+
def response
|
8
|
+
path = File.file?(@a[0]) ? @a[0] : "#{@a[0]}/#{@r[@match.length-1..-1].join('/')}".gsub(/\/+/,'/')
|
9
|
+
|
10
|
+
if File.directory?(path)
|
11
|
+
@status = 404
|
12
|
+
return []
|
13
|
+
end
|
14
|
+
if File.exists?(path)
|
15
|
+
mtime = File.mtime(path)
|
16
|
+
@headers << Riddl::Header.new("Last-Modified",mtime.httpdate)
|
17
|
+
@headers << Riddl::Header.new("ETag",Digest::MD5.hexdigest(mtime.httpdate))
|
18
|
+
htime = @env["HTTP_IF_MODIFIED_SINCE"].nil? ? Time.at(0) : Time.parse(@env["HTTP_IF_MODIFIED_SINCE"])
|
19
|
+
if htime == mtime
|
20
|
+
@headers << Riddl::Header.new("Connection","close")
|
21
|
+
@status = 304 # Not modified
|
22
|
+
return []
|
23
|
+
else
|
24
|
+
return Riddl::Parameter::Complex.new("file",MIME::Types.type_for(path).first.to_s,File.open(path,'r'))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
@status = 404
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,310 @@
|
|
1
|
+
module Riddl
|
2
|
+
module Utils
|
3
|
+
module Notifications
|
4
|
+
|
5
|
+
module Producer
|
6
|
+
|
7
|
+
def self::implementation(backend,handler=nil,details=:production)
|
8
|
+
unless handler.nil? || (handler.is_a? Riddl::Utils::Notifications::Producer::HandlerBase)
|
9
|
+
raise "handler not a subclass of HandlerBase"
|
10
|
+
end
|
11
|
+
Proc.new do
|
12
|
+
on resource "notifications" do
|
13
|
+
run Riddl::Utils::Notifications::Producer::Overview if get
|
14
|
+
on resource "topics" do
|
15
|
+
run Riddl::Utils::Notifications::Producer::Topics, backend if get
|
16
|
+
end
|
17
|
+
on resource "subscriptions" do
|
18
|
+
run Riddl::Utils::Notifications::Producer::Subscriptions, backend, details if get
|
19
|
+
run Riddl::Utils::Notifications::Producer::CreateSubscription, backend, handler if post 'subscribe'
|
20
|
+
on resource do
|
21
|
+
run Riddl::Utils::Notifications::Producer::Subscription, backend, details if get 'request'
|
22
|
+
run Riddl::Utils::Notifications::Producer::UpdateSubscription, backend, handler if put 'details'
|
23
|
+
run Riddl::Utils::Notifications::Producer::DeleteSubscription, backend, handler if delete 'delete'
|
24
|
+
on resource 'ws' do
|
25
|
+
run Riddl::Utils::Notifications::Producer::WS, backend, handler if websocket
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class HandlerBase #{{{
|
34
|
+
def initialize(data)
|
35
|
+
@data = data
|
36
|
+
@key = nil
|
37
|
+
@topics = []
|
38
|
+
end
|
39
|
+
def key(k)
|
40
|
+
@key = k
|
41
|
+
self
|
42
|
+
end
|
43
|
+
def topics(t)
|
44
|
+
@topics = t
|
45
|
+
self
|
46
|
+
end
|
47
|
+
def ws_open(socket); end
|
48
|
+
def ws_close; end
|
49
|
+
def ws_message(socket,data); end
|
50
|
+
def create; end
|
51
|
+
def delete; end
|
52
|
+
def update; end
|
53
|
+
end #}}}
|
54
|
+
|
55
|
+
class Backend #{{{
|
56
|
+
attr_reader :topics
|
57
|
+
|
58
|
+
class Sub #{{{
|
59
|
+
def initialize(name)
|
60
|
+
@name = name
|
61
|
+
end
|
62
|
+
def modify(&block)
|
63
|
+
XML::Smart.modify(@name,"<subscription xmlns='http://riddl.org/ns/common-patterns/notifications-producer/1.0'/>") do |doc|
|
64
|
+
doc.register_namespace 'n', 'http://riddl.org/ns/common-patterns/notifications-producer/1.0'
|
65
|
+
block.call doc
|
66
|
+
end
|
67
|
+
end
|
68
|
+
def delete
|
69
|
+
FileUtils::rm_rf(File.dirname(@name))
|
70
|
+
end
|
71
|
+
def to_s
|
72
|
+
File.read(@name)
|
73
|
+
end
|
74
|
+
def view(&block)
|
75
|
+
XML::Smart.open_unprotected(@name) do |doc|
|
76
|
+
doc.register_namespace 'n', 'http://riddl.org/ns/common-patterns/notifications-producer/1.0'
|
77
|
+
block.call doc
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end #}}}
|
81
|
+
|
82
|
+
class Subs #{{{
|
83
|
+
def initialize(target)
|
84
|
+
@target = target
|
85
|
+
end
|
86
|
+
|
87
|
+
def each(&block)
|
88
|
+
keys.each do |key|
|
89
|
+
doc = XML::Smart.open_unprotected(@target + '/' + key + '/subscription.xml')
|
90
|
+
doc.register_namespace 'n', 'http://riddl.org/ns/common-patterns/notifications-producer/1.0'
|
91
|
+
block.call doc, key
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def [](key)
|
96
|
+
f = @target + '/' + key + '/subscription.xml'
|
97
|
+
File.exists?(f) ? Sub.new(f) : nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def create(&block)
|
101
|
+
key = nil
|
102
|
+
begin
|
103
|
+
continue = true
|
104
|
+
key = Digest::MD5.hexdigest(Kernel::rand().to_s)
|
105
|
+
Dir.mkdir(@target + '/' + key) rescue continue = false
|
106
|
+
end until continue
|
107
|
+
producer_secret = Digest::MD5.hexdigest(Kernel::rand().to_s)
|
108
|
+
consumer_secret = Digest::MD5.hexdigest(Kernel::rand().to_s)
|
109
|
+
File.open(@target + '/' + key + '/producer-secret','w') { |f| f.write producer_secret }
|
110
|
+
File.open(@target + '/' + key + '/consumer-secret','w') { |f| f.write consumer_secret }
|
111
|
+
XML::Smart::modify(@target + '/' + key + '/subscription.xml',"<subscription xmlns='http://riddl.org/ns/common-patterns/notifications-producer/1.0'/>") do |doc|
|
112
|
+
block.call doc, key
|
113
|
+
end
|
114
|
+
[key, producer_secret, consumer_secret]
|
115
|
+
end
|
116
|
+
|
117
|
+
def keys
|
118
|
+
if File.directory?(@target)
|
119
|
+
Dir[@target + '/*'].map do |d|
|
120
|
+
File.directory?(d) ? File.basename(d) : nil
|
121
|
+
end.compact
|
122
|
+
else
|
123
|
+
[]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end #}}}
|
127
|
+
|
128
|
+
def initialize(topics,target)
|
129
|
+
@target = target.gsub(/^\/+/,'/')
|
130
|
+
|
131
|
+
FileUtils::mkdir_p(@target) unless File.exists?(@target)
|
132
|
+
|
133
|
+
raise "topics file not found" unless File.exists?(topics)
|
134
|
+
@topics = XML::Smart.open_unprotected(topics.gsub(/^\/+/,'/'))
|
135
|
+
@topics.register_namespace 'n', 'http://riddl.org/ns/common-patterns/notifications-producer/1.0'
|
136
|
+
end
|
137
|
+
|
138
|
+
def subscriptions
|
139
|
+
Subs.new(@target)
|
140
|
+
end
|
141
|
+
|
142
|
+
end #}}}
|
143
|
+
|
144
|
+
class Overview < Riddl::Implementation #{{{
|
145
|
+
def response
|
146
|
+
Riddl::Parameter::Complex.new("overview","text/xml") do
|
147
|
+
<<-END
|
148
|
+
<overview xmlns='http://riddl.org/ns/common-patterns/notifications-producer/1.0'>
|
149
|
+
<topics/>
|
150
|
+
<subscriptions/>
|
151
|
+
</overview>
|
152
|
+
END
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end #}}}
|
157
|
+
|
158
|
+
class Topics < Riddl::Implementation #{{{
|
159
|
+
def response
|
160
|
+
backend = @a[0]
|
161
|
+
Riddl::Parameter::Complex.new("overview","text/xml") do
|
162
|
+
backend.topics.to_s
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end #}}}
|
166
|
+
|
167
|
+
class Subscriptions < Riddl::Implementation #{{{
|
168
|
+
def response
|
169
|
+
backend = @a[0]
|
170
|
+
details = @a[1]
|
171
|
+
Riddl::Parameter::Complex.new("subscriptions","text/xml") do
|
172
|
+
ret = XML::Smart::string <<-END
|
173
|
+
<subscriptions details='#{details}' xmlns='http://riddl.org/ns/common-patterns/notifications-producer/1.0'/>
|
174
|
+
END
|
175
|
+
backend.subscriptions.each do |doc,key|
|
176
|
+
if doc.root.attributes['url']
|
177
|
+
ret.root.add('subscription', :id => key, :url => doc.root.attributes['url'])
|
178
|
+
else
|
179
|
+
ret.root.add('subscription', :id => key)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
ret.to_s
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end #}}}
|
186
|
+
|
187
|
+
class Subscription < Riddl::Implementation #{{{
|
188
|
+
def response
|
189
|
+
backend = @a[0]
|
190
|
+
Riddl::Parameter::Complex.new("subscription","text/xml") do
|
191
|
+
backend.subscriptions[@r.last].to_s
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end #}}}
|
195
|
+
|
196
|
+
class CreateSubscription < Riddl::Implementation #{{{
|
197
|
+
def response
|
198
|
+
backend = @a[0]
|
199
|
+
handler = @a[1]
|
200
|
+
|
201
|
+
url = @p[0].name == 'url' ? @p.shift.value : nil
|
202
|
+
|
203
|
+
topics = []
|
204
|
+
key, consumer_secret, producer_secret = backend.subscriptions.create do |doc,key|
|
205
|
+
doc.root.attributes['url'] = url if url
|
206
|
+
while @p.length > 0
|
207
|
+
topic = @p.shift.value
|
208
|
+
base = @p.shift
|
209
|
+
type = base.name
|
210
|
+
items = base.value.split(',')
|
211
|
+
t = if topics.include?(topic)
|
212
|
+
doc.find("/n:subscription/n:topic[@id='#{topic}']").first
|
213
|
+
else
|
214
|
+
topics << topic
|
215
|
+
doc.root.add('topic', :id => topic)
|
216
|
+
end
|
217
|
+
items.each do |i|
|
218
|
+
t.add(type[0..-2], i)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
handler.key(key).topics(topics).create unless handler.nil?
|
224
|
+
[
|
225
|
+
Riddl::Parameter::Simple.new('key',key),
|
226
|
+
Riddl::Parameter::Simple.new('producer-secret',producer_secret),
|
227
|
+
Riddl::Parameter::Simple.new('consumer-secret',consumer_secret)
|
228
|
+
]
|
229
|
+
end
|
230
|
+
end #}}}
|
231
|
+
|
232
|
+
class DeleteSubscription < Riddl::Implementation #{{{
|
233
|
+
def response
|
234
|
+
backend = @a[0]
|
235
|
+
handler = @a[1]
|
236
|
+
key = @r.last
|
237
|
+
|
238
|
+
backend.subscriptions[key].delete
|
239
|
+
handler.key(key).delete unless handler.nil?
|
240
|
+
return
|
241
|
+
end
|
242
|
+
end #}}}
|
243
|
+
|
244
|
+
class UpdateSubscription < Riddl::Implementation #{{{
|
245
|
+
def response
|
246
|
+
backend = @a[0]
|
247
|
+
handler = @a[1]
|
248
|
+
key = @r.last
|
249
|
+
|
250
|
+
muid = @p.shift.value
|
251
|
+
url = @p[0].name == 'url' ? @p.shift.value : nil
|
252
|
+
|
253
|
+
# TODO check if message is valid (with producer secret)
|
254
|
+
unless backend.subscriptions[key]
|
255
|
+
@status = 404
|
256
|
+
return # subscription not found
|
257
|
+
end
|
258
|
+
|
259
|
+
topics = []
|
260
|
+
backend.subscriptions[key].modify do |doc|
|
261
|
+
if url.nil?
|
262
|
+
doc.find('/n:subscription/@url').delete_all!
|
263
|
+
else
|
264
|
+
doc.root.attributes['url'] = url
|
265
|
+
end
|
266
|
+
doc.root.children.delete_all!
|
267
|
+
while @p.length > 1
|
268
|
+
topic = @p.shift.value
|
269
|
+
base = @p.shift
|
270
|
+
type = base.name
|
271
|
+
items = base.value.split(',')
|
272
|
+
t = if topics.include?(topic)
|
273
|
+
doc.find("/n:subscription/n:topic[@id='#{topic}']").first
|
274
|
+
else
|
275
|
+
topics << topic
|
276
|
+
doc.root.add('topic', :id => topic)
|
277
|
+
end
|
278
|
+
items.each do |i|
|
279
|
+
t.add(type[0..-2], i)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
handler.key(key).topics(topics).update unless handler.nil?
|
285
|
+
nil
|
286
|
+
end
|
287
|
+
end #}}}
|
288
|
+
|
289
|
+
class WS < Riddl::WebSocketImplementation #{{{
|
290
|
+
def onopen
|
291
|
+
@backend = @a[0]
|
292
|
+
@handler = @a[1]
|
293
|
+
@key = @r[-2]
|
294
|
+
@handler.key(@key).ws_open(self) unless @handler.nil?
|
295
|
+
end
|
296
|
+
|
297
|
+
def onmessage(data)
|
298
|
+
@handler.key(@key).ws_message(data) unless @handler.nil?
|
299
|
+
end
|
300
|
+
|
301
|
+
def onclose
|
302
|
+
@handler.key(@key).ws_close() unless @handler.nil?
|
303
|
+
end
|
304
|
+
end #}}}
|
305
|
+
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
@@ -0,0 +1,474 @@
|
|
1
|
+
module Riddl
|
2
|
+
module Utils
|
3
|
+
module Properties
|
4
|
+
|
5
|
+
VERSION_MAJOR = 1
|
6
|
+
VERSION_MINOR = 0
|
7
|
+
PROPERTIES_SCHEMA_XSL_RNG = "#{File.dirname(__FILE__)}/../ns/common-patterns/properties/#{VERSION_MAJOR}.#{VERSION_MINOR}/properties.schema.xsl"
|
8
|
+
|
9
|
+
def self::implementation(backend,handler=nil,details=:production)
|
10
|
+
unless handler.nil? || (handler.is_a? Riddl::Utils::Properties::HandlerBase)
|
11
|
+
raise "handler not a subclass of HandlerBase"
|
12
|
+
end
|
13
|
+
Proc.new do
|
14
|
+
run Riddl::Utils::Properties::All, backend, handler if get '*'
|
15
|
+
run Riddl::Utils::Properties::Query, backend, handler if get 'query'
|
16
|
+
on resource 'schema' do
|
17
|
+
run Riddl::Utils::Properties::Schema, backend if get
|
18
|
+
on resource 'rng' do
|
19
|
+
run Riddl::Utils::Properties::RngSchema, backend if get
|
20
|
+
end
|
21
|
+
end
|
22
|
+
on resource 'values' do
|
23
|
+
run Riddl::Utils::Properties::Properties, backend, handler if get
|
24
|
+
run Riddl::Utils::Properties::AddProperty, backend, handler if post 'property'
|
25
|
+
run Riddl::Utils::Properties::AddProperties, backend, handler if put 'properties'
|
26
|
+
on resource do
|
27
|
+
run Riddl::Utils::Properties::GetContent, backend, handler if get
|
28
|
+
run Riddl::Utils::Properties::DelContent, backend, handler if delete
|
29
|
+
run Riddl::Utils::Properties::AddContent, backend, handler if post 'addcontent'
|
30
|
+
run Riddl::Utils::Properties::UpdContent, backend, handler if put 'updcontent'
|
31
|
+
on resource do
|
32
|
+
run Riddl::Utils::Properties::GetContent, backend, handler if get
|
33
|
+
run Riddl::Utils::Properties::DelContent, backend, handler if delete
|
34
|
+
run Riddl::Utils::Properties::UpdContent, backend, handler if put 'updcontent'
|
35
|
+
on resource do
|
36
|
+
run Riddl::Utils::Properties::GetContent, backend, handler if get
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Overloadable and Backends
|
45
|
+
class HandlerBase #{{{
|
46
|
+
def initialize(data)
|
47
|
+
@data = data
|
48
|
+
@property = nil
|
49
|
+
end
|
50
|
+
def property(p)
|
51
|
+
@property = p
|
52
|
+
self
|
53
|
+
end
|
54
|
+
def create; end
|
55
|
+
def read; end
|
56
|
+
def update; end
|
57
|
+
def delete; end
|
58
|
+
end #}}}
|
59
|
+
|
60
|
+
class Backend #{{{
|
61
|
+
attr_reader :schema, :data, :rng
|
62
|
+
|
63
|
+
def initialize(schema,target,init=nil)
|
64
|
+
@target = target.gsub(/^\/+/,'/')
|
65
|
+
@schemas = {}
|
66
|
+
@rngs = {}
|
67
|
+
|
68
|
+
if schema.is_a? Hash
|
69
|
+
schema.each { |k,v| add_schema k, v }
|
70
|
+
elsif schema.is_a? String
|
71
|
+
add_schema 'default', schema
|
72
|
+
end
|
73
|
+
raise "no schemas provided" if @schemas.length == 0
|
74
|
+
@schema = @schemas.first[1]
|
75
|
+
@rng = @rngs.first[1]
|
76
|
+
|
77
|
+
FileUtils::mkdir_p(File::dirname(@target)) unless File.exists?(@target)
|
78
|
+
FileUtils::cp init, @target if init and not File.exists?(@target)
|
79
|
+
|
80
|
+
raise "properties file not found" unless File.exists?(@target)
|
81
|
+
@data = XML::Smart.open_unprotected(@target)
|
82
|
+
@data.register_namespace 'p', 'http://riddl.org/ns/common-patterns/properties/1.0'
|
83
|
+
@mutex = Mutex.new
|
84
|
+
end
|
85
|
+
|
86
|
+
def activate_schema(name)
|
87
|
+
if @schemas[name]
|
88
|
+
@schema = @schemas[name]
|
89
|
+
@rng = @rngs[name]
|
90
|
+
true
|
91
|
+
else
|
92
|
+
false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_schema(key,name)
|
97
|
+
raise "schema file not found" unless File.exists?(name)
|
98
|
+
@schemas[key] = XML::Smart.open_unprotected(name.gsub(/^\/+/,'/'))
|
99
|
+
@schemas[key].register_namespace 'p', 'http://riddl.org/ns/common-patterns/properties/1.0'
|
100
|
+
if !File::exists?(Riddl::Utils::Properties::PROPERTIES_SCHEMA_XSL_RNG)
|
101
|
+
raise "properties schema transformation file not found"
|
102
|
+
end
|
103
|
+
@rngs[key] = @schemas[key].transform_with(XML::Smart.open_unprotected(Riddl::Utils::Properties::PROPERTIES_SCHEMA_XSL_RNG))
|
104
|
+
end
|
105
|
+
private :add_schema
|
106
|
+
|
107
|
+
def modifiable?(property)
|
108
|
+
@schema.find("boolean(/p:properties/p:#{property}[@modifiable='true'])") || schema.find("boolean(/p:properties/p:optional/p:#{property}[@modifiable='true'])")
|
109
|
+
end
|
110
|
+
def valid_state?(property,current,new)
|
111
|
+
@schema.find("boolean(/p:properties/p:#{property}/p:#{current}/p:#{new}[@putable='true'])") || schema.find("boolean(/p:properties/p:optional/p:#{property}/p:#{current}/p:#{new}[@putable='true'])")
|
112
|
+
end
|
113
|
+
def is_state?(property)
|
114
|
+
@schema.find("boolean(/p:properties/p:#{property}[@type='state'])") || schema.find("boolean(/p:properties/p:optional/p:#{property}[@type='state'])")
|
115
|
+
end
|
116
|
+
def init_state?(property,new)
|
117
|
+
@schema.find("boolean(/p:properties/p:#{property}/p:#{new}[position()=1])") || schema.find("boolean(/p:properties/p:optional/p:#{property}/p:#{new}[position()=1])")
|
118
|
+
end
|
119
|
+
def property_type(property)
|
120
|
+
exis = @schema.find("/p:properties/*[name()='#{property}']|/p:properties/p:optional/*[name()='#{property}']")
|
121
|
+
exis.any? ? exis.first.attributes['type'].to_sym : nil
|
122
|
+
end
|
123
|
+
|
124
|
+
def modify(&block)
|
125
|
+
tdoc = @data.root.to_doc
|
126
|
+
tdoc.register_namespace 'p', 'http://riddl.org/ns/common-patterns/properties/1.0'
|
127
|
+
@mutex.synchronize do
|
128
|
+
block.call tdoc
|
129
|
+
if tdoc.validate_against(@rng)
|
130
|
+
block.call @data
|
131
|
+
@data.save_as(@target)
|
132
|
+
true
|
133
|
+
else
|
134
|
+
false
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end #}}}
|
139
|
+
|
140
|
+
# Just reading
|
141
|
+
class All < Riddl::Implementation #{{{
|
142
|
+
def response
|
143
|
+
backend = @a[0]
|
144
|
+
handler = @a[1]
|
145
|
+
handler.read unless handler.nil?
|
146
|
+
return Riddl::Parameter::Complex.new("document","text/xml",backend.data.to_s)
|
147
|
+
end
|
148
|
+
end #}}}
|
149
|
+
|
150
|
+
class Properties < Riddl::Implementation #{{{
|
151
|
+
def response
|
152
|
+
backend = @a[0]
|
153
|
+
handler = @a[1]
|
154
|
+
handler.read unless handler.nil?
|
155
|
+
|
156
|
+
ret = XML::Smart.string("<properties xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>")
|
157
|
+
backend.schema.find("/p:properties/*[name()!='optional']|/p:properties/p:optional/*").each do |r|
|
158
|
+
ret.root.add("property",r.qname.to_s)
|
159
|
+
end
|
160
|
+
return Riddl::Parameter::Complex.new("keys","text/xml",ret.to_s)
|
161
|
+
end
|
162
|
+
end #}}}
|
163
|
+
|
164
|
+
class Query < Riddl::Implementation #{{{
|
165
|
+
def response
|
166
|
+
backend = @a[0]
|
167
|
+
handler = @a[1]
|
168
|
+
handler.read unless handler.nil?
|
169
|
+
query = (@p[0].value.to_s.strip.empty? ? '*' : @p[0].value)
|
170
|
+
|
171
|
+
begin
|
172
|
+
e = backend.data.find(query)
|
173
|
+
rescue => e
|
174
|
+
prop = XML::Smart::string("<not-existing xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>").to_s
|
175
|
+
return Riddl::Parameter::Complex.new("value","text/xml",prop.to_s)
|
176
|
+
end
|
177
|
+
if e.class == XML::Smart::Dom::NodeSet
|
178
|
+
if e.any?
|
179
|
+
prop = XML::Smart::string("<value xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>")
|
180
|
+
prop.root.add(e)
|
181
|
+
else
|
182
|
+
prop = XML::Smart::string("<not-existing xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>").to_s
|
183
|
+
end
|
184
|
+
return Riddl::Parameter::Complex.new("value","text/xml",prop.to_s)
|
185
|
+
else
|
186
|
+
return Riddl::Parameter::Simple.new("value",e.to_s)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end #}}}
|
190
|
+
|
191
|
+
class RngSchema < Riddl::Implementation #{{{
|
192
|
+
def response
|
193
|
+
backend = @a[0]
|
194
|
+
Riddl::Parameter::Complex.new("document-schema","text/xml",backend.rng.to_s)
|
195
|
+
end
|
196
|
+
end #}}}
|
197
|
+
|
198
|
+
class Schema < Riddl::Implementation #{{{
|
199
|
+
def response
|
200
|
+
backend = @a[0]
|
201
|
+
return Riddl::Parameter::Complex.new("document-schema","text/xml",backend.schema.to_s)
|
202
|
+
end
|
203
|
+
end #}}}
|
204
|
+
|
205
|
+
class GetContent < Riddl::Implementation #{{{
|
206
|
+
def response
|
207
|
+
backend = @a[0]
|
208
|
+
handler = @a[1]
|
209
|
+
|
210
|
+
handler.property(@r[1]).read unless handler.nil?
|
211
|
+
|
212
|
+
if ret = extract_values(backend,@r[1],Riddl::Protocols::HTTP::Parser::unescape(@r[2..-1].join('/')))
|
213
|
+
ret
|
214
|
+
else
|
215
|
+
@status = 404
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def extract_values(backend,property,minor=nil)
|
220
|
+
case backend.property_type(property)
|
221
|
+
when :complex
|
222
|
+
res = backend.data.find("/p:properties/*[name()=\"#{property}\"]#{minor == '' ? '' : "/p:#{minor}"}")
|
223
|
+
if res.any?
|
224
|
+
prop = XML::Smart::string("<value xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>")
|
225
|
+
if res.length == 1
|
226
|
+
prop.root.add(res.first.children)
|
227
|
+
else
|
228
|
+
prop.root.add(res)
|
229
|
+
end
|
230
|
+
return Riddl::Parameter::Complex.new("value","text/xml",prop.to_s)
|
231
|
+
else
|
232
|
+
prop = XML::Smart::string("<not-existing xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>")
|
233
|
+
end
|
234
|
+
when :simple, :state
|
235
|
+
res = backend.data.find("string(/p:properties/*[name()=\"#{property}\"]#{minor})")
|
236
|
+
return Riddl::Parameter::Simple.new("value",res.to_s)
|
237
|
+
when :arbitrary
|
238
|
+
res = backend.data.find("/p:properties/*[name()=\"#{property}\"]")
|
239
|
+
if res.any?
|
240
|
+
c = res.first.children
|
241
|
+
if c.length == 1 && c.first.class == XML::Smart::Dom::Element
|
242
|
+
return Riddl::Parameter::Complex.new("content","text/xml",c.first.dump)
|
243
|
+
else
|
244
|
+
return Riddl::Parameter::Complex.new("content","text/plain",c.first.to_s)
|
245
|
+
end
|
246
|
+
else
|
247
|
+
prop = XML::Smart::string("<not-existing xmlns=\"http://riddl.org/ns/common-patterns/properties/1.0\"/>")
|
248
|
+
return Riddl::Parameter::Complex.new("content","text/xml",prop.to_s)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
nil
|
252
|
+
end
|
253
|
+
private :extract_values
|
254
|
+
|
255
|
+
end #}}}
|
256
|
+
|
257
|
+
# Modifiable
|
258
|
+
class AddProperty < Riddl::Implementation #{{{
|
259
|
+
def response
|
260
|
+
backend = @a[0]
|
261
|
+
handler = @a[1]
|
262
|
+
|
263
|
+
property = @p[0].value
|
264
|
+
ct = @p[1]
|
265
|
+
value = ct.name == 'value' ? ct.value : nil
|
266
|
+
content = ct.name == 'content' ? ct.value : nil
|
267
|
+
|
268
|
+
unless backend.modifiable?(property)
|
269
|
+
@status = 500
|
270
|
+
return # change properties.schema
|
271
|
+
end
|
272
|
+
|
273
|
+
path = "/p:properties/*[name()=\"#{property}\"]"
|
274
|
+
nodes = backend.data.find(path)
|
275
|
+
if nodes.any?
|
276
|
+
@status = 404
|
277
|
+
return # this property does not exist
|
278
|
+
end
|
279
|
+
|
280
|
+
if backend.is_state?(property)
|
281
|
+
unless backend.init_state?(property,value)
|
282
|
+
@status = 404
|
283
|
+
return # not a valid state from here on
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
newstuff = value.nil? ? XML::Smart.string(content).root.children : value
|
288
|
+
backend.modify do |doc|
|
289
|
+
ele = doc.root.add property
|
290
|
+
if value.nil?
|
291
|
+
ele.add newstuff
|
292
|
+
else
|
293
|
+
ele.text = newstuff
|
294
|
+
end
|
295
|
+
end || begin
|
296
|
+
@status = 400
|
297
|
+
return # bad request
|
298
|
+
end
|
299
|
+
|
300
|
+
handler.property(property).create unless handler.nil?
|
301
|
+
return
|
302
|
+
end
|
303
|
+
end #}}}
|
304
|
+
|
305
|
+
class AddProperties < Riddl::Implementation #{{{
|
306
|
+
def response
|
307
|
+
backend = @a[0]
|
308
|
+
handler = @a[1]
|
309
|
+
|
310
|
+
0.upto(@p.length/2-1) do |i|
|
311
|
+
property = @p[i*2].value
|
312
|
+
ct = @p[i*2+1]
|
313
|
+
value = ct.name == 'value' ? ct.value : nil
|
314
|
+
content = ct.name == 'content' ? ct.value : nil
|
315
|
+
|
316
|
+
unless backend.modifiable?(property)
|
317
|
+
@status = 500
|
318
|
+
return # change properties.schema
|
319
|
+
end
|
320
|
+
|
321
|
+
path = "/p:properties/*[name()=\"#{property}\"]"
|
322
|
+
nodes = backend.data.find(path)
|
323
|
+
if nodes.empty?
|
324
|
+
@status = 404
|
325
|
+
return # this property does not exist
|
326
|
+
end
|
327
|
+
|
328
|
+
if backend.is_state?(property)
|
329
|
+
unless backend.valid_state?(property,nodes.first.to_s,value)
|
330
|
+
@status = 404
|
331
|
+
return # not a valid state from here on
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
newstuff = value.nil? ? XML::Smart.string(content).root.children : value
|
336
|
+
backend.modify do |doc|
|
337
|
+
nodes = doc.find(path)
|
338
|
+
nods = nodes.map{|ele| ele.children.delete_all!; ele}
|
339
|
+
nods.each do |ele|
|
340
|
+
if value.nil?
|
341
|
+
ele.add newstuff
|
342
|
+
else
|
343
|
+
ele.text = newstuff
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end || begin
|
347
|
+
@status = 400
|
348
|
+
return # bad request
|
349
|
+
end
|
350
|
+
|
351
|
+
handler.property(property).create unless handler.nil?
|
352
|
+
end
|
353
|
+
return
|
354
|
+
end
|
355
|
+
end #}}}
|
356
|
+
|
357
|
+
class AddContent < Riddl::Implementation #{{{
|
358
|
+
def response
|
359
|
+
backend = @a[0]
|
360
|
+
handler = @a[1]
|
361
|
+
|
362
|
+
property = @r[1]
|
363
|
+
value = @p.detect{|p| p.name == 'value'}.value
|
364
|
+
|
365
|
+
unless backend.modifiable?(property)
|
366
|
+
@status = 500
|
367
|
+
return # change properties.schema
|
368
|
+
end
|
369
|
+
|
370
|
+
path = "/p:properties/p:#{property}"
|
371
|
+
node = backend.data.find(path)
|
372
|
+
if node.empty?
|
373
|
+
@status = 404
|
374
|
+
return # this property does not exist
|
375
|
+
end
|
376
|
+
|
377
|
+
newstuff = XML::Smart.string(value)
|
378
|
+
backend.modify do |doc|
|
379
|
+
node = doc.find(path)
|
380
|
+
node.first.add newstuff.root
|
381
|
+
end || begin
|
382
|
+
@status = 400
|
383
|
+
return # bad request
|
384
|
+
end
|
385
|
+
|
386
|
+
handler.property(property).create unless handler.nil?
|
387
|
+
end
|
388
|
+
end #}}}
|
389
|
+
|
390
|
+
class DelContent < Riddl::Implementation #{{{
|
391
|
+
def response
|
392
|
+
backend = @a[0]
|
393
|
+
handler = @a[1]
|
394
|
+
|
395
|
+
property = @r[1]
|
396
|
+
minor = Riddl::Protocols::HTTP::Parser::unescape(@r[2])
|
397
|
+
|
398
|
+
unless backend.modifiable?(property)
|
399
|
+
@status = 500
|
400
|
+
return # change properties.schema
|
401
|
+
end
|
402
|
+
|
403
|
+
path = "/p:properties/*[name()=\"#{property}\"]#{minor.nil? ? '' : "/p:#{minor}"}"
|
404
|
+
nodes = backend.data.find(path)
|
405
|
+
if nodes.empty?
|
406
|
+
@status = 404
|
407
|
+
return # this property does not exist
|
408
|
+
end
|
409
|
+
|
410
|
+
backend.modify do |doc|
|
411
|
+
doc.find(path).delete_all!
|
412
|
+
end || begin
|
413
|
+
@status = 400
|
414
|
+
return # bad request
|
415
|
+
end
|
416
|
+
|
417
|
+
handler.property(property).delete unless handler.nil?
|
418
|
+
return
|
419
|
+
end
|
420
|
+
end #}}}
|
421
|
+
|
422
|
+
class UpdContent < Riddl::Implementation #{{{
|
423
|
+
def response
|
424
|
+
backend = @a[0]
|
425
|
+
handler = @a[1]
|
426
|
+
|
427
|
+
property = @r[1]
|
428
|
+
value = @p.detect{|p| p.name == 'value'}; value = value.nil? ? value : value.value
|
429
|
+
content = @p.detect{|p| p.name == 'content'}; content = content.nil? ? content : content.value
|
430
|
+
minor = @r[2]
|
431
|
+
|
432
|
+
unless backend.modifiable?(property)
|
433
|
+
@status = 500
|
434
|
+
return # change properties.schema
|
435
|
+
end
|
436
|
+
|
437
|
+
path = "/p:properties/*[name()=\"#{property}\"]#{minor.nil? ? '' : "/p:#{minor}"}"
|
438
|
+
nodes = backend.data.find(path)
|
439
|
+
if nodes.empty?
|
440
|
+
@status = 404
|
441
|
+
return # this property does not exist
|
442
|
+
end
|
443
|
+
|
444
|
+
if backend.is_state?(property)
|
445
|
+
unless backend.valid_state?(property,nodes.first.to_s,value)
|
446
|
+
@status = 404
|
447
|
+
return # not a valid state from here on
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
newstuff = value.nil? ? XML::Smart.string(content).root.children : value
|
452
|
+
backend.modify do |doc|
|
453
|
+
nodes = doc.root.find(path)
|
454
|
+
nods = nodes.map{|ele| ele.children.delete_all!; ele}
|
455
|
+
nods.each do |ele|
|
456
|
+
if value.nil?
|
457
|
+
ele.add newstuff
|
458
|
+
else
|
459
|
+
ele.text = newstuff
|
460
|
+
end
|
461
|
+
end
|
462
|
+
end || begin
|
463
|
+
@status = 400
|
464
|
+
return # bad request
|
465
|
+
end
|
466
|
+
|
467
|
+
handler.property(property).update unless handler.nil?
|
468
|
+
return
|
469
|
+
end
|
470
|
+
end #}}}
|
471
|
+
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|