riddl 0.99.218 → 0.99.219
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ruby/riddl/description.rb +33 -0
- data/lib/ruby/riddl/docoverlay.html +255 -0
- data/lib/ruby/riddl/docoverlay.rb +14 -0
- data/lib/ruby/riddl/downloadify.rb +14 -0
- data/lib/ruby/riddl/erbserve.rb +24 -0
- data/lib/ruby/riddl/fileserve.rb +45 -0
- data/lib/ruby/riddl/notifications_producer.rb +328 -0
- data/lib/ruby/riddl/oauth2-helper.rb +135 -0
- data/lib/ruby/riddl/oauth2-univie.rb +167 -0
- data/lib/ruby/riddl/properties.rb +479 -0
- data/lib/ruby/riddl/turtle.rb +72 -0
- data/lib/ruby/riddl/utils/oauth2-univie.rb +3 -3
- data/lib/ruby/riddl/xmlserve.rb +40 -0
- data/lib/ruby/riddl/xsloverlay.rb +21 -0
- data/riddl.gemspec +1 -1
- metadata +14 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5a7fd98e32be12a9b153657c3959ac670be0e3f
|
4
|
+
data.tar.gz: d743380ae7ebab04a1c4d8ddcdb1d3acfdb156c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1e1424f7bb9820af80dbfe314071f8a9f81584eb7ee13579c5571e0bcb63909a506f6c5ed12f9d78372bc1e8233dc5ce7f37fce2b273801faf029f2557ffc86
|
7
|
+
data.tar.gz: 454f78db0320ad93dece4b885ec3796ae267f40cad9f3061d5fe9676658a983624bb49d93e28ad05b7f5b9bc1acf9fc20bbd74bbd3a9054825ea8957992db0f3
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../client')
|
2
|
+
|
3
|
+
module Riddl
|
4
|
+
module Utils
|
5
|
+
module Description
|
6
|
+
|
7
|
+
class RDR < Riddl::Implementation
|
8
|
+
def response
|
9
|
+
@headers << Riddl::Header.new("RIDDL_DESCRIPTION", 'oh my, a riddl description')
|
10
|
+
return Riddl::Parameter::Complex.new("riddl-description","text/xml",@a[0])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Call < Riddl::Implementation
|
15
|
+
def response
|
16
|
+
client = Riddl::Client.new @a[3]
|
17
|
+
|
18
|
+
path = client.resource "/" + @a[4]
|
19
|
+
@status, result, headers = if @a[0].nil?
|
20
|
+
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
|
21
|
+
else
|
22
|
+
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
|
23
|
+
end
|
24
|
+
headers.each do |k,v|
|
25
|
+
@headers << Riddl::Header.new(k,v) unless ["CONTENT_TYPE", "CONTENT_DISPOSITION", "RIDDL_TYPE", "CONTENT_ID", "CONTENT_LENGTH", "CONNECTION", "SERVER"].include?(k)
|
26
|
+
end
|
27
|
+
result
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8"/>
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=EDGE"/>
|
6
|
+
|
7
|
+
<title>Documentation</title>
|
8
|
+
|
9
|
+
<style>
|
10
|
+
body {
|
11
|
+
counter-reset: h1counter;
|
12
|
+
}
|
13
|
+
h1 { font-size: 1.5em; counter-reset: h2counter; }
|
14
|
+
h2 { font-size: 1.2em; }
|
15
|
+
h3 { font-size: 1.0em }
|
16
|
+
h1:before {
|
17
|
+
content: counter(h1counter) ".\0000a0\0000a0";
|
18
|
+
counter-increment: h1counter;
|
19
|
+
}
|
20
|
+
h2:before {
|
21
|
+
content: counter(h1counter) "." counter(h2counter) ".\0000a0\0000a0";
|
22
|
+
counter-increment: h2counter;
|
23
|
+
}
|
24
|
+
.code {
|
25
|
+
border: 1px dashed #425d73; background-color: #DEE6EE; margin:0px; padding: 0.5em 1em; font-family: monospace; font-size:0.9em;
|
26
|
+
}
|
27
|
+
</style>
|
28
|
+
|
29
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
|
30
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
|
31
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js"></script>
|
32
|
+
<script src="https://cdn.rawgit.com/vkiryukhin/vkBeautify/master/vkbeautify.js"></script>
|
33
|
+
<script>//<![CDATA[
|
34
|
+
$.fn.serializeXML = function(){
|
35
|
+
var out = '';
|
36
|
+
if (typeof XMLSerializer == 'function') {
|
37
|
+
var xs = new XMLSerializer();
|
38
|
+
this.each(function() {
|
39
|
+
out += xs.serializeToString(this);
|
40
|
+
});
|
41
|
+
} else if (this[0] && this[0].xml != 'undefined') {
|
42
|
+
this.each(function() {
|
43
|
+
out += this.xml;
|
44
|
+
});
|
45
|
+
}
|
46
|
+
return out;
|
47
|
+
};
|
48
|
+
|
49
|
+
function docChildren(path,name,data){
|
50
|
+
var ret= [];
|
51
|
+
var subject = path ? $(path,data) : $(data);
|
52
|
+
subject.children().each(function(k,ele){
|
53
|
+
if (ele.localName == name && ele.namespaceURI == "http://riddl.org/ns/documentation/1.0") {
|
54
|
+
ret.push(ele);
|
55
|
+
}
|
56
|
+
});
|
57
|
+
return $(ret);
|
58
|
+
}
|
59
|
+
|
60
|
+
function codeFormat(temp) {
|
61
|
+
temp = temp.replace(/\&/g,"&").replace(/\"/g,""").replace(/>/g,">").replace(/</g,"<").replace(/'/g,"'");
|
62
|
+
temp = temp.replace(/\t/g,' ');
|
63
|
+
temp = temp.replace(/\r/g,'');
|
64
|
+
temp = temp.replace(/^$/g,' ');
|
65
|
+
temp = temp.replace(/^ */gm,function mf(m){
|
66
|
+
var tm = (m.length + 2) * 0.6 + 2 * 0.6;
|
67
|
+
return "<div style='text-indent:-" + tm + "em;margin-left:" + tm + "em'>" + (Array(m.length + 1).join(" "));
|
68
|
+
});
|
69
|
+
temp = temp.replace(/ /g,"  ");
|
70
|
+
temp = temp.replace(/\n/g,"</div>\n");
|
71
|
+
temp += "</div>\n";
|
72
|
+
temp = temp.replace(/href="([^&]+)"/g,'href="<a href="/__doc_includes/$1">$1</a>"')
|
73
|
+
return temp;
|
74
|
+
}
|
75
|
+
|
76
|
+
$(document).ready(function(){
|
77
|
+
var rpath = "<%= @h['RIDDL_DECLARATION_PATH'] %>";
|
78
|
+
var pathele = _.compact(rpath.split('/'));
|
79
|
+
|
80
|
+
var t = $('#breadcrumblink')[0];
|
81
|
+
var link = "";
|
82
|
+
_.each(pathele,function(e){
|
83
|
+
link = link + "/" + e;
|
84
|
+
var clone = document.importNode(t.content, true);
|
85
|
+
$('a',clone).attr('href',link + "/?doc=true");
|
86
|
+
$('a',clone).text(e);
|
87
|
+
$('#breadcrumb').append(clone);
|
88
|
+
});
|
89
|
+
|
90
|
+
$.ajax({
|
91
|
+
url: rpath + "?riddl-resource-description",
|
92
|
+
success: function(data){
|
93
|
+
$('#main').append(marked(docChildren('description > resource','documentation',data).text()));
|
94
|
+
|
95
|
+
var subr = $('description > resource > resource',data);
|
96
|
+
if (subr.length == 0) {
|
97
|
+
$('#subresources').remove();
|
98
|
+
} else {
|
99
|
+
$('description > resource > resource',data).each(function(k,ele){
|
100
|
+
var sum = docChildren(null,'documentation',ele).attr('summary');
|
101
|
+
var exa = docChildren(null,'example',ele);
|
102
|
+
exa = exa.length == 0 ? null : exa.text();
|
103
|
+
if (exa) {
|
104
|
+
var rell = exa;
|
105
|
+
if ($(ele).attr('relative')) {
|
106
|
+
var relt = exa + ' (' + $(ele).attr('relative') + ')';
|
107
|
+
} else {
|
108
|
+
var relt = '{} (any string possible)';
|
109
|
+
}
|
110
|
+
} else {
|
111
|
+
var rell = $(ele).attr('relative');
|
112
|
+
var relt = _.trim(rell);
|
113
|
+
}
|
114
|
+
var t = $('#subresourceitem')[0];
|
115
|
+
var clone = document.importNode(t.content, true);
|
116
|
+
$('a',clone).attr('href','./' + rell + '/?doc=true');
|
117
|
+
if (relt != "") {
|
118
|
+
$('a',clone).text(relt);
|
119
|
+
}
|
120
|
+
if (sum) { $('.content',clone).text(sum); }
|
121
|
+
$('#subresources .anchor').append(clone);
|
122
|
+
});
|
123
|
+
}
|
124
|
+
|
125
|
+
var oprs = $('description > resource > get, description > resource > post, description > resource > put, description > resource > delete, description > resource > request',data);
|
126
|
+
if (oprs.length == 0) {
|
127
|
+
$('#operations').remove();
|
128
|
+
} else {
|
129
|
+
oprs.each(function(k,ele){
|
130
|
+
var sum = docChildren(null,'documentation',ele).attr('summary');
|
131
|
+
var details = docChildren(null,'documentation',ele).text();
|
132
|
+
var example = docChildren(null,'example',ele);
|
133
|
+
var ex = null;
|
134
|
+
|
135
|
+
var tin = $(ele).attr('in');
|
136
|
+
var tout = $(ele).attr('out');
|
137
|
+
var whatl = $(ele).prop('tagName');
|
138
|
+
var whatd = tin == "*" ? '' : '( ' + tin + ' )';
|
139
|
+
whatd += tout ? ' : ' + tout : '';
|
140
|
+
|
141
|
+
var tinmess = "";
|
142
|
+
if (tin && tin != "*") {
|
143
|
+
$('description > message[name="' + tin + '"]',data).each(function(k,mess){
|
144
|
+
tinmess = mess;
|
145
|
+
_.each(mess.attributes,function(attr){
|
146
|
+
if (attr.localName == 'example' && attr.namespaceURI == "http://riddl.org/ns/documentation/1.0") {
|
147
|
+
ex = attr.nodeValue;
|
148
|
+
}
|
149
|
+
});
|
150
|
+
});
|
151
|
+
}
|
152
|
+
if (example.length > 0) { ex = example.text(); }
|
153
|
+
|
154
|
+
var toutmess = "";
|
155
|
+
if (tout && tout != "*") {
|
156
|
+
$('description > message[name="' + tout + '"]',data).each(function(k,mess){
|
157
|
+
toutmess = mess;
|
158
|
+
});
|
159
|
+
}
|
160
|
+
|
161
|
+
// Overview
|
162
|
+
var t = $('#operationitem')[0];
|
163
|
+
var clone = document.importNode(t.content, true);
|
164
|
+
$('.what .link',clone).text(whatl);
|
165
|
+
$('.what .link',clone).attr('href','#' + (whatl + '_' + whatd).replace(/ /g,'_'));
|
166
|
+
$('.what .details',clone).text(whatd);
|
167
|
+
if (ex != null) {
|
168
|
+
if (ex == "")
|
169
|
+
$('.what .try a',clone).attr('href',window.location.pathname);
|
170
|
+
else
|
171
|
+
$('.what .try a',clone).attr('href','?' + ex);
|
172
|
+
} else {
|
173
|
+
$('.what .try',clone).remove();
|
174
|
+
}
|
175
|
+
if (sum) { $('.content',clone).text(sum); }
|
176
|
+
$('#operations .anchor').append(clone);
|
177
|
+
|
178
|
+
//Details
|
179
|
+
var t = $('#operationsec')[0];
|
180
|
+
var clone = document.importNode(t.content, true);
|
181
|
+
$('.what',clone).text(whatl + ' ' + whatd);
|
182
|
+
$('.what',clone).attr('id',(whatl + '_' + whatd).replace(/ /g,'_'));
|
183
|
+
if (_.trim(details) == "") {
|
184
|
+
$('.text',clone).text(sum);
|
185
|
+
} else {
|
186
|
+
$('.text',clone).append(marked(details));
|
187
|
+
}
|
188
|
+
if (ex != null) {
|
189
|
+
if (ex == "") {
|
190
|
+
$('.try a',clone).attr('href',window.location.pathname);
|
191
|
+
} else {
|
192
|
+
$('.try a',clone).attr('href','?' + ex);
|
193
|
+
$('.try a',clone).text('?' + ex);
|
194
|
+
}
|
195
|
+
} else {
|
196
|
+
$('.try',clone).remove();
|
197
|
+
}
|
198
|
+
|
199
|
+
if (tin && tin != "*") {
|
200
|
+
$('.input .message',clone).html(codeFormat(vkbeautify.xml($(tinmess).serializeXML(),' ')));
|
201
|
+
} else {
|
202
|
+
$('.input',clone).remove();
|
203
|
+
}
|
204
|
+
if (tout) {
|
205
|
+
$('.output .message',clone).html(codeFormat(vkbeautify.xml($(toutmess).serializeXML(),' ')));
|
206
|
+
} else {
|
207
|
+
$('.output',clone).remove();
|
208
|
+
}
|
209
|
+
$('#operations').append(clone);
|
210
|
+
});
|
211
|
+
}
|
212
|
+
}
|
213
|
+
});
|
214
|
+
});
|
215
|
+
//]]></script>
|
216
|
+
</head>
|
217
|
+
<body>
|
218
|
+
<template id="breadcrumblink"><span> / </span><a href=""></a></template>
|
219
|
+
<template id="subresourceitem"><li><div class='what'><a class='details' href=''>Example missing</a></div><div class="content"></div></li></template>
|
220
|
+
<template id="operationitem"><li><div class='what'><a class='link' href=''></a> <span class='details'></span> <span class='try'>(<a href=''>Try it!</a>)</span></div><div class="content"></div></li></template>
|
221
|
+
<template id="operationsec">
|
222
|
+
<section>
|
223
|
+
<h2 class="what" id=""></h2>
|
224
|
+
<p class="try">Example: <a href=""><i>no parameters</i></a></p>
|
225
|
+
<p class="text"></p>
|
226
|
+
<div class="input">
|
227
|
+
<h3>Input</h3>
|
228
|
+
<div class='message code'></div>
|
229
|
+
</div>
|
230
|
+
<div class="output">
|
231
|
+
<h3>Output</h3>
|
232
|
+
<div class='message code'></div>
|
233
|
+
</div>
|
234
|
+
</section>
|
235
|
+
</template>
|
236
|
+
|
237
|
+
<nav id="breadcrumb">[<a href="/">TOP</a>]</nav>
|
238
|
+
<section id="main"></section>
|
239
|
+
<section id="subresources">
|
240
|
+
<h1>Sub-Resources</h1>
|
241
|
+
<p>From here on you can go to the following sub resources:</p>
|
242
|
+
<ul class="anchor"></ul>
|
243
|
+
</section>
|
244
|
+
<section id="operations">
|
245
|
+
<h1>Operations</h1>
|
246
|
+
<section>
|
247
|
+
<p>For this resource the following operations are possible:</p>
|
248
|
+
<ul class="anchor"></ul>
|
249
|
+
</section>
|
250
|
+
</section>
|
251
|
+
</body>
|
252
|
+
</html>
|
253
|
+
|
254
|
+
|
255
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Riddl
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
class DocOverlay < Riddl::Implementation
|
7
|
+
def response
|
8
|
+
rval = ERB.new(File.read(File.dirname(__FILE__) + '/docoverlay.html'), 0, "%<>")
|
9
|
+
Riddl::Parameter::Complex.new('documentationresponse','text/html',rval.result(binding))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
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,24 @@
|
|
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
|
+
input = @a[1]
|
10
|
+
if File.directory?(path)
|
11
|
+
@status = 404
|
12
|
+
return []
|
13
|
+
end
|
14
|
+
if File.exists?(path)
|
15
|
+
__ERB_FILE__ = path
|
16
|
+
rval = ERB.new(File.read(path), 0, "%<>")
|
17
|
+
return Riddl::Parameter::Complex.new("data",MIME::Types.type_for(path)[0].to_s,rval.result(binding))
|
18
|
+
end
|
19
|
+
@status = 404
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'mime/types'
|
2
|
+
require 'charlock_holmes'
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
module Riddl
|
6
|
+
module Utils
|
7
|
+
class FileServe < Riddl::Implementation
|
8
|
+
def response
|
9
|
+
path = File.file?(@a[0]) ? @a[0] : "#{@a[0]}/#{@r[@match.length-1..-1].join('/')}".gsub(/\/+/,'/')
|
10
|
+
|
11
|
+
if File.directory?(path)
|
12
|
+
@status = 404
|
13
|
+
return []
|
14
|
+
end
|
15
|
+
if File.exists?(path)
|
16
|
+
mtime = File.mtime(path)
|
17
|
+
@headers << Riddl::Header.new("Last-Modified",mtime.httpdate)
|
18
|
+
@headers << Riddl::Header.new("ETag",Digest::MD5.hexdigest(mtime.httpdate))
|
19
|
+
htime = @env["HTTP_IF_MODIFIED_SINCE"].nil? ? Time.at(0) : Time.parse(@env["HTTP_IF_MODIFIED_SINCE"])
|
20
|
+
if htime == mtime
|
21
|
+
@headers << Riddl::Header.new("Connection","close")
|
22
|
+
@status = 304 # Not modified
|
23
|
+
return []
|
24
|
+
else
|
25
|
+
fmt = @a[1] || begin
|
26
|
+
mt = MIME::Types.type_for(path).first
|
27
|
+
if mt.nil?
|
28
|
+
'text/plain;charset=utf-8'
|
29
|
+
else
|
30
|
+
apx = ''
|
31
|
+
if mt.ascii?
|
32
|
+
tstr = File.read(path,CharlockHolmes::EncodingDetector::DEFAULT_BINARY_SCAN_LEN)
|
33
|
+
apx = ';charset=' + CharlockHolmes::EncodingDetector.detect(tstr)[:encoding]
|
34
|
+
end
|
35
|
+
mt.to_s + apx
|
36
|
+
end
|
37
|
+
end
|
38
|
+
return Riddl::Parameter::Complex.new('file',fmt,File.open(path,'r'))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
@status = 404
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,328 @@
|
|
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 read(&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
|
+
f = @target + '/' + key + '/subscription.xml'
|
90
|
+
block.call Sub.new(f), key if File.exists? f
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def include?(key)
|
95
|
+
f = @target + '/' + key + '/subscription.xml'
|
96
|
+
File.exists?(f)
|
97
|
+
end
|
98
|
+
|
99
|
+
def [](key)
|
100
|
+
f = @target + '/' + key + '/subscription.xml'
|
101
|
+
File.exists?(f) ? Sub.new(f) : nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def create(&block)
|
105
|
+
key = nil
|
106
|
+
begin
|
107
|
+
continue = true
|
108
|
+
key = Digest::MD5.hexdigest(Kernel::rand().to_s)
|
109
|
+
Dir.mkdir(@target + '/' + key) rescue continue = false
|
110
|
+
end until continue
|
111
|
+
producer_secret = Digest::MD5.hexdigest(Kernel::rand().to_s)
|
112
|
+
consumer_secret = Digest::MD5.hexdigest(Kernel::rand().to_s)
|
113
|
+
File.open(@target + '/' + key + '/producer-secret','w') { |f| f.write producer_secret }
|
114
|
+
File.open(@target + '/' + key + '/consumer-secret','w') { |f| f.write consumer_secret }
|
115
|
+
XML::Smart::modify(@target + '/' + key + '/subscription.xml',"<subscription xmlns='http://riddl.org/ns/common-patterns/notifications-producer/1.0'/>") do |doc|
|
116
|
+
doc.register_namespace 'n', 'http://riddl.org/ns/common-patterns/notifications-producer/1.0'
|
117
|
+
block.call doc, key
|
118
|
+
end
|
119
|
+
[key, producer_secret, consumer_secret]
|
120
|
+
end
|
121
|
+
|
122
|
+
def keys
|
123
|
+
if File.directory?(@target)
|
124
|
+
Dir[@target + '/*'].map do |d|
|
125
|
+
File.directory?(d) ? File.basename(d) : nil
|
126
|
+
end.compact
|
127
|
+
else
|
128
|
+
[]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end #}}}
|
132
|
+
|
133
|
+
def initialize(topics,target)
|
134
|
+
@target = target.gsub(/^\/+/,'/')
|
135
|
+
|
136
|
+
FileUtils::mkdir_p(@target) unless File.exists?(@target)
|
137
|
+
|
138
|
+
raise "topics file not found" unless File.exists?(topics)
|
139
|
+
@topics = XML::Smart.open_unprotected(topics.gsub(/^\/+/,'/'))
|
140
|
+
@topics.register_namespace 'n', 'http://riddl.org/ns/common-patterns/notifications-producer/1.0'
|
141
|
+
|
142
|
+
subscriptions.each do |sub,key|
|
143
|
+
sub.read do |doc|
|
144
|
+
if doc.find('/*[@url]').empty?
|
145
|
+
sub.delete
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def subscriptions
|
152
|
+
Subs.new(@target)
|
153
|
+
end
|
154
|
+
|
155
|
+
end #}}}
|
156
|
+
|
157
|
+
class Overview < Riddl::Implementation #{{{
|
158
|
+
def response
|
159
|
+
Riddl::Parameter::Complex.new("overview","text/xml") do
|
160
|
+
<<-END
|
161
|
+
<overview xmlns='http://riddl.org/ns/common-patterns/notifications-producer/1.0'>
|
162
|
+
<topics/>
|
163
|
+
<subscriptions/>
|
164
|
+
</overview>
|
165
|
+
END
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end #}}}
|
170
|
+
|
171
|
+
class Topics < Riddl::Implementation #{{{
|
172
|
+
def response
|
173
|
+
backend = @a[0]
|
174
|
+
Riddl::Parameter::Complex.new("overview","text/xml") do
|
175
|
+
backend.topics.to_s
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end #}}}
|
179
|
+
|
180
|
+
class Subscriptions < Riddl::Implementation #{{{
|
181
|
+
def response
|
182
|
+
backend = @a[0]
|
183
|
+
details = @a[1]
|
184
|
+
Riddl::Parameter::Complex.new("subscriptions","text/xml") do
|
185
|
+
ret = XML::Smart::string <<-END
|
186
|
+
<subscriptions details='#{details}' xmlns='http://riddl.org/ns/common-patterns/notifications-producer/1.0'/>
|
187
|
+
END
|
188
|
+
backend.subscriptions.each do |sub,key|
|
189
|
+
sub.read do |doc|
|
190
|
+
if doc.root.attributes['url']
|
191
|
+
ret.root.add('subscription', :id => key, :url => doc.root.attributes['url'])
|
192
|
+
else
|
193
|
+
ret.root.add('subscription', :id => key)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
ret.to_s
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end #}}}
|
201
|
+
|
202
|
+
class Subscription < Riddl::Implementation #{{{
|
203
|
+
def response
|
204
|
+
backend = @a[0]
|
205
|
+
if(backend.subscriptions.include?(@r.last))
|
206
|
+
Riddl::Parameter::Complex.new("subscription","text/xml") do
|
207
|
+
backend.subscriptions[@r.last].to_s
|
208
|
+
end
|
209
|
+
else
|
210
|
+
@status = 404
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end #}}}
|
214
|
+
|
215
|
+
class CreateSubscription < Riddl::Implementation #{{{
|
216
|
+
def response
|
217
|
+
backend = @a[0]
|
218
|
+
handler = @a[1]
|
219
|
+
|
220
|
+
url = @p[0].name == 'url' ? @p.shift.value : nil
|
221
|
+
|
222
|
+
topics = []
|
223
|
+
key, consumer_secret, producer_secret = backend.subscriptions.create do |doc,key|
|
224
|
+
doc.root.attributes['url'] = url if url
|
225
|
+
while @p.length > 0
|
226
|
+
topic = @p.shift.value
|
227
|
+
base = @p.shift
|
228
|
+
type = base.name
|
229
|
+
items = base.value.split(',')
|
230
|
+
t = if topics.include?(topic)
|
231
|
+
doc.find("/n:subscription/n:topic[@id='#{topic}']").first
|
232
|
+
else
|
233
|
+
topics << topic
|
234
|
+
doc.root.add('topic', :id => topic)
|
235
|
+
end
|
236
|
+
items.each do |i|
|
237
|
+
t.add(type[0..-2], i)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
handler.key(key).topics(topics).create unless handler.nil?
|
243
|
+
[
|
244
|
+
Riddl::Parameter::Simple.new('key',key),
|
245
|
+
Riddl::Parameter::Simple.new('producer-secret',producer_secret),
|
246
|
+
Riddl::Parameter::Simple.new('consumer-secret',consumer_secret)
|
247
|
+
]
|
248
|
+
end
|
249
|
+
end #}}}
|
250
|
+
|
251
|
+
class DeleteSubscription < Riddl::Implementation #{{{
|
252
|
+
def response
|
253
|
+
backend = @a[0]
|
254
|
+
handler = @a[1]
|
255
|
+
key = @r.last
|
256
|
+
|
257
|
+
backend.subscriptions[key].delete
|
258
|
+
handler.key(key).delete unless handler.nil?
|
259
|
+
return
|
260
|
+
end
|
261
|
+
end #}}}
|
262
|
+
|
263
|
+
class UpdateSubscription < Riddl::Implementation #{{{
|
264
|
+
def response
|
265
|
+
backend = @a[0]
|
266
|
+
handler = @a[1]
|
267
|
+
key = @r.last
|
268
|
+
|
269
|
+
url = @p[0].name == 'url' ? @p.shift.value : nil
|
270
|
+
|
271
|
+
# TODO check if message is valid (with producer secret)
|
272
|
+
unless backend.subscriptions[key]
|
273
|
+
@status = 404
|
274
|
+
return # subscription not found
|
275
|
+
end
|
276
|
+
|
277
|
+
topics = []
|
278
|
+
backend.subscriptions[key].modify do |doc|
|
279
|
+
if url.nil?
|
280
|
+
doc.find('/n:subscription/@url').delete_all!
|
281
|
+
else
|
282
|
+
doc.root.attributes['url'] = url
|
283
|
+
end
|
284
|
+
doc.root.children.delete_all!
|
285
|
+
while @p.length > 1
|
286
|
+
topic = @p.shift.value
|
287
|
+
base = @p.shift
|
288
|
+
type = base.name
|
289
|
+
items = base.value.split(',')
|
290
|
+
t = if topics.include?(topic)
|
291
|
+
doc.find("/n:subscription/n:topic[@id='#{topic}']").first
|
292
|
+
else
|
293
|
+
topics << topic
|
294
|
+
doc.root.add('topic', :id => topic)
|
295
|
+
end
|
296
|
+
items.each do |i|
|
297
|
+
t.add(type[0..-2], i)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
handler.key(key).topics(topics).update unless handler.nil?
|
303
|
+
nil
|
304
|
+
end
|
305
|
+
end #}}}
|
306
|
+
|
307
|
+
class WS < Riddl::WebSocketImplementation #{{{
|
308
|
+
def onopen
|
309
|
+
@backend = @a[0]
|
310
|
+
@handler = @a[1]
|
311
|
+
@key = @r[-2]
|
312
|
+
@handler.key(@key).ws_open(self) unless @handler.nil?
|
313
|
+
end
|
314
|
+
|
315
|
+
def onmessage(data)
|
316
|
+
@handler.key(@key).ws_message(data) unless @handler.nil?
|
317
|
+
end
|
318
|
+
|
319
|
+
def onclose
|
320
|
+
@handler.key(@key).ws_close() unless @handler.nil?
|
321
|
+
end
|
322
|
+
end #}}}
|
323
|
+
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|