riddl 0.99.218 → 0.99.219
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.
- 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
|