cpee-logging-xes-yaml 1.4.1 → 1.5.1
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/cpee-logging-xes-yaml.gemspec +1 -1
- data/lib/cpee-logging-xes-yaml/access.xml +36 -0
- data/lib/cpee-logging-xes-yaml/implementation.rb +365 -0
- data/lib/cpee-logging-xes-yaml/{logging.xml → implementation.xml} +6 -0
- data/lib/cpee-logging-xes-yaml/tools.rb +57 -162
- data/server/log +1 -1
- metadata +4 -3
- data/lib/cpee-logging-xes-yaml/logging.rb +0 -160
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2f390c43e6c32b2abead85f3ead4fa971dda1cb24226c784a7048024c364d53
|
|
4
|
+
data.tar.gz: 4a5b8ffceb7522522568a8499d35118942dfb74a4521df7e45ebec646667cd20
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9b472f1e1258dd2c6d88156a088c1e97e79b208bea4b59df1933b5f818443094c19b16c2f6c6f9d4510d785bfb7c9c8bcb1288adcf8f148e3c8ce6d2e10efc06
|
|
7
|
+
data.tar.gz: b5d446c669f4e2e95323dd3c319d97926dca9690746b852855b5ce90b12c91711a6a63e4f4c4c62a9a6db655a68fde18d9e2c28571b57e17de1796b5078df250
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
This file is part of CPEE-LOGGING-XES-YAML.
|
|
3
|
+
|
|
4
|
+
CPEE-LOGGING-XES-YAML is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU Lesser General Public License as published by the Free
|
|
6
|
+
Software Foundation, either version 3 of the License, or (at your option) any
|
|
7
|
+
later version.
|
|
8
|
+
|
|
9
|
+
CPEE-LOGGING-XES-YAML is distributed in the hope that it will be useful, but
|
|
10
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
11
|
+
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
|
12
|
+
more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU Lesser General Public License along with
|
|
15
|
+
CPEE-LOGGING-XES-YAML (file LICENSE in the main directory). If not, see
|
|
16
|
+
<http://www.gnu.org/licenses/>.
|
|
17
|
+
-->
|
|
18
|
+
|
|
19
|
+
<description datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes" xmlns="http://riddl.org/ns/description/1.0" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:doc="http://cpee.org/ns/documentation">
|
|
20
|
+
|
|
21
|
+
<message name="yaml">
|
|
22
|
+
<parameter name="log" mimetype="text/yaml"/>
|
|
23
|
+
</message>
|
|
24
|
+
<message name="xml">
|
|
25
|
+
<parameter name="log" mimetype="application/xml"/>
|
|
26
|
+
</message>
|
|
27
|
+
|
|
28
|
+
<resource>
|
|
29
|
+
<resource relative="[a-f0-9-]+.xes.yaml">
|
|
30
|
+
<get out="yaml"/>
|
|
31
|
+
</resource>
|
|
32
|
+
<resource relative="[a-f0-9-]+.xes.xml">
|
|
33
|
+
<get out="xml"/>
|
|
34
|
+
</resource>
|
|
35
|
+
</resource>
|
|
36
|
+
</description>
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# This file is part of CPEE-LOGGING-XES-YAML.
|
|
2
|
+
#
|
|
3
|
+
# CPEE-LOGGING-XES-YAML is free software: you can redistribute it and/or modify it
|
|
4
|
+
# under the terms of the GNU Lesser General Public License as published by the Free
|
|
5
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
|
6
|
+
# later version.
|
|
7
|
+
#
|
|
8
|
+
# CPEE-LOGGING-XES-YAML is distributed in the hope that it will be useful, but
|
|
9
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
|
11
|
+
# more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU Lesser General Public License along with
|
|
14
|
+
# CPEE-LOGGING-XES-YAML (file LICENSE in the main directory). If not, see
|
|
15
|
+
# <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
require 'redis'
|
|
18
|
+
require 'json'
|
|
19
|
+
require 'yaml'
|
|
20
|
+
require 'riddl/server'
|
|
21
|
+
require 'time'
|
|
22
|
+
|
|
23
|
+
require_relative 'tools'
|
|
24
|
+
|
|
25
|
+
module CPEE
|
|
26
|
+
module Logging
|
|
27
|
+
|
|
28
|
+
SERVER = File.expand_path(File.join(__dir__,'implementation.xml'))
|
|
29
|
+
|
|
30
|
+
TEMPLATE_XES_XML_START = <<-END
|
|
31
|
+
<log xmlns="http://www.xes-standard.org/" xes.version="2.0" xes.features="nested-attributes">
|
|
32
|
+
<string key="creator" value="cpee.org"/>
|
|
33
|
+
<extension name="Time" prefix="time" uri="http://www.xes-standard.org/time.xesext"/>
|
|
34
|
+
<extension name="Concept" prefix="concept" uri="http://www.xes-standard.org/concept.xesext"/>
|
|
35
|
+
<extension name="ID" prefix="id" uri="http://www.xes-standard.org/identity.xesext"/>
|
|
36
|
+
<extension name="Lifecycle" prefix="lifecycle" uri="http://www.xes-standard.org/lifecycle.xesext"/>
|
|
37
|
+
<extension name="CPEE" prefix="cpee" uri="http://cpee.org/cpee.xesext"/>
|
|
38
|
+
<extension name="stream" prefix="stream" uri="https://cpee.org/datastream/datastream.xesext"/>
|
|
39
|
+
<global scope="trace">
|
|
40
|
+
<string key="concept:name" value="__NOTSPECIFIED__"/>
|
|
41
|
+
<string key="cpee:name" value="__NOTSPECIFIED__"/>
|
|
42
|
+
</global>
|
|
43
|
+
<global scope="event">
|
|
44
|
+
<string key="concept:name" value="__NOTSPECIFIED__"/>
|
|
45
|
+
<string key="concept:instance" value="-1"/>
|
|
46
|
+
<string key="concept:endpoint" value="__NOTSPECIFIED__"/>
|
|
47
|
+
<string key="id:id" value="__NOTSPECIFIED__"/>
|
|
48
|
+
<string key="lifecycle:transition" value="complete" />
|
|
49
|
+
<string key="cpee:lifecycle:transition" value="activity/calling"/>
|
|
50
|
+
<date key="time:timestamp" value="__NOTSPECIFIED__"/>
|
|
51
|
+
</global>
|
|
52
|
+
END
|
|
53
|
+
TEMPLATE_XES_XML_TRC = <<-END
|
|
54
|
+
<trace xmlns="http://www.xes-standard.org/"/>
|
|
55
|
+
END
|
|
56
|
+
TEMPLATE_XES_XML_EVT = <<-END
|
|
57
|
+
<event xmlns="http://www.xes-standard.org/"/>
|
|
58
|
+
END
|
|
59
|
+
TEMPLATE_XES_XML_MID = <<-END
|
|
60
|
+
<trace>
|
|
61
|
+
END
|
|
62
|
+
TEMPLATE_XES_XML_END = <<-END
|
|
63
|
+
</trace>
|
|
64
|
+
</log>
|
|
65
|
+
END
|
|
66
|
+
|
|
67
|
+
class HeaderAndFile #{{{
|
|
68
|
+
def initialize(header, io)
|
|
69
|
+
@header = header
|
|
70
|
+
@io = io
|
|
71
|
+
@position = 0
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def read(length = nil, outbuf = nil)
|
|
75
|
+
if @position < @header.bytesize
|
|
76
|
+
data = read_header(length)
|
|
77
|
+
|
|
78
|
+
if length && data.bytesize < length
|
|
79
|
+
remaining_length = length - data.bytesize
|
|
80
|
+
file_data = @io.read
|
|
81
|
+
data << file_data if file_data
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
data = @io.read(length)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
return data.nil? || data.empty? && length ? nil : append_to_outbuf(data, outbuf)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def rewind
|
|
91
|
+
@io&.rewind
|
|
92
|
+
@position = 0 # returning position is the way rewind does it
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def close
|
|
96
|
+
@io&.close
|
|
97
|
+
@io = nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def read_header(length)
|
|
101
|
+
chunk = if length
|
|
102
|
+
@header.byteslice(@position, length)
|
|
103
|
+
else
|
|
104
|
+
@header.byteslice(@position..-1)
|
|
105
|
+
end
|
|
106
|
+
@position += chunk.bytesize
|
|
107
|
+
chunk
|
|
108
|
+
end
|
|
109
|
+
private :read_header
|
|
110
|
+
|
|
111
|
+
def append_to_outbuf(data, outbuf)
|
|
112
|
+
outbuf ? outbuf.replace(data || '') : data
|
|
113
|
+
end
|
|
114
|
+
private :append_to_outbuf
|
|
115
|
+
end #}}}
|
|
116
|
+
|
|
117
|
+
class DownloadYAML < Riddl::Implementation
|
|
118
|
+
def response
|
|
119
|
+
opts = @a[0]
|
|
120
|
+
fname = File.join(opts[:log_dir],@r[-1])
|
|
121
|
+
if File.exist?(fname)
|
|
122
|
+
io = File.open fname
|
|
123
|
+
header = File.read(fname.sub(/yaml$/,'header')) if File.exist?(fname.sub(/yaml$/,'header'))
|
|
124
|
+
Riddl::Parameter::Complex::new('log','text/yaml',HeaderAndFile.new(header || '',File.open(fname)))
|
|
125
|
+
else
|
|
126
|
+
@status = 404
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
class DownloadXML < Riddl::Implementation
|
|
132
|
+
def self::rec_type(it) #{{{
|
|
133
|
+
if it.is_a?(String) && it =~ /^[\dT:+.-]+$/ && (Time.parse(it) rescue nil)
|
|
134
|
+
'x:date'
|
|
135
|
+
elsif it.is_a? Float
|
|
136
|
+
'x:float'
|
|
137
|
+
elsif it.is_a? Integer
|
|
138
|
+
'x:int'
|
|
139
|
+
elsif it.is_a? String
|
|
140
|
+
'x:string'
|
|
141
|
+
end
|
|
142
|
+
end #}}}
|
|
143
|
+
|
|
144
|
+
def self::format_secs(s) #{{{
|
|
145
|
+
return 'long' if s.infinite?
|
|
146
|
+
s = s.to_i
|
|
147
|
+
m = s / 60
|
|
148
|
+
m < 0 ? "#{s}s" : "#{'%02d' % m}m #{'%02d' % (s%60)}s"
|
|
149
|
+
end #}}}
|
|
150
|
+
|
|
151
|
+
def self::rec_a_insert(event,node,level=0) #{{{
|
|
152
|
+
event.each do |i|
|
|
153
|
+
tnode = node
|
|
154
|
+
case i
|
|
155
|
+
when Hash
|
|
156
|
+
tnode = node.add('x:list', 'key' => 'element')
|
|
157
|
+
self::rec_insert(i,tnode,level+1)
|
|
158
|
+
when Array
|
|
159
|
+
tnode = node.add('x:list', 'key' => 'element')
|
|
160
|
+
self::rec_insert(i,tnode,level+1)
|
|
161
|
+
when String
|
|
162
|
+
node.add(rec_type(i), 'key' => 'element', 'value' => (i.empty? ? "__UNSPECIFIED__" : i))
|
|
163
|
+
when Integer, Float
|
|
164
|
+
node.add(rec_type(i), 'key' => 'element', 'value' => i)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end #}}}
|
|
168
|
+
|
|
169
|
+
def self::rec_insert(event,node,level=0) #{{{
|
|
170
|
+
event.each do |k,v|
|
|
171
|
+
case v
|
|
172
|
+
when String
|
|
173
|
+
node.add(rec_type(v), 'key' => k, 'value' => (v.empty? ? "__UNSPECIFIED__" : v))
|
|
174
|
+
when Integer
|
|
175
|
+
node.add(rec_type(v), 'key' => k, 'value' => v)
|
|
176
|
+
when Float
|
|
177
|
+
node.add(rec_type(v), 'key' => k, 'value' => v)
|
|
178
|
+
when Array
|
|
179
|
+
tnode = node.add('x:list', 'key' => k)
|
|
180
|
+
self::rec_a_insert(v,tnode,level+1)
|
|
181
|
+
when Hash
|
|
182
|
+
tnode = node.add('x:list', 'key' => k)
|
|
183
|
+
self::rec_insert(v,tnode)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end #}}}
|
|
187
|
+
|
|
188
|
+
def response
|
|
189
|
+
opts = @a[0]
|
|
190
|
+
fname = File.join(opts[:log_dir],@r[-1]).sub(/xml$/,'yaml')
|
|
191
|
+
if File.exist?(fname)
|
|
192
|
+
body = StringIO.new
|
|
193
|
+
|
|
194
|
+
body.write(TEMPLATE_XES_XML_START)
|
|
195
|
+
|
|
196
|
+
io = File.open(fname)
|
|
197
|
+
YAML.load_stream(io) do |e|
|
|
198
|
+
if trace = e.dig('log','trace')
|
|
199
|
+
xml = XML::Smart.string(TEMPLATE_XES_XML_TRC)
|
|
200
|
+
xml.register_namespace 'x', 'http://www.xes-standard.org/'
|
|
201
|
+
trace.each do |t,tv|
|
|
202
|
+
xml.find('//x:trace').each do |ele|
|
|
203
|
+
ele.add('x:string', 'key' => t, 'value' => tv)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
body.write(' ' + xml.root.dump.gsub(/\n/,"\n ") + "\n")
|
|
207
|
+
body.write(TEMPLATE_XES_XML_MID)
|
|
208
|
+
end
|
|
209
|
+
if e.dig('event')
|
|
210
|
+
xml = XML::Smart.string(TEMPLATE_XES_XML_EVT)
|
|
211
|
+
xml.register_namespace 'x', 'http://www.xes-standard.org/'
|
|
212
|
+
DownloadXML::rec_insert(e.dig('event'),xml.root)
|
|
213
|
+
body.write(' ' + xml.root.dump.gsub(/\n/,"\n ") + "\n")
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
body.write(TEMPLATE_XES_XML_END)
|
|
217
|
+
body.rewind
|
|
218
|
+
|
|
219
|
+
Riddl::Parameter::Complex::new('log','application/xml',body)
|
|
220
|
+
else
|
|
221
|
+
@status = 404
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
class Handler < Riddl::Implementation
|
|
227
|
+
def response
|
|
228
|
+
opts = @a[0]
|
|
229
|
+
type = @p[0].value
|
|
230
|
+
topic = @p[1].value
|
|
231
|
+
event_name = @p[2].value
|
|
232
|
+
payload = @p[3].value.read
|
|
233
|
+
|
|
234
|
+
### we write headers into its own file. If race condition at first, no problemo
|
|
235
|
+
unless File.exist? File.join(opts[:log_dir],@h['CPEE_INSTANCE_UUID'] + '.xes.header')
|
|
236
|
+
notification = JSON.parse(payload)
|
|
237
|
+
log = YAML::load(File.read(opts[:template]))
|
|
238
|
+
log['log']['trace']['concept:name'] ||= notification['instance']
|
|
239
|
+
log['log']['trace']['cpee:name'] ||= notification['instance-name'] if notification['instance-name']
|
|
240
|
+
log['log']['trace']['cpee:instance'] ||= notification['instance-uuid']
|
|
241
|
+
log['log']['trace']['cpee:parent_instance'] ||= notification.dig('content','attributes','parent_instance').to_i if notification.dig('content','attributes','parent_instance')
|
|
242
|
+
log['log']['trace']['cpee:parent_instance_uuid'] ||= notification.dig('content','attributes','parent_instance_uuid') if notification.dig('content','attributes','parent_instance_uuid')
|
|
243
|
+
log['log']['trace']['cpee:parent_instance_model'] ||= notification.dig('content','attributes','parent_instance_model') if notification.dig('content','attributes','parent_instance_model')
|
|
244
|
+
log['log']['trace']['cpee:parent_instance_task_id'] ||= notification.dig('content','attributes','parent_instance_task_id') if notification.dig('content','attributes','parent_instance_task_id')
|
|
245
|
+
log['log']['trace']['cpee:parent_instance_task_label'] ||= notification.dig('content','attributes','parent_instance_task_label') if notification.dig('content','attributes','parent_instance_task_label')
|
|
246
|
+
File.open(File.join(opts[:log_dir],@h['CPEE_INSTANCE_UUID']+'.xes.header'),'w'){|f| f.puts log.to_yaml}
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
EM.defer do
|
|
250
|
+
CPEE::Logging::forward opts, topic, event_name, payload
|
|
251
|
+
end if type == 'event'
|
|
252
|
+
EM.defer do
|
|
253
|
+
CPEE::Logging::doc opts, topic, event_name, payload
|
|
254
|
+
end
|
|
255
|
+
nil
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
class Overview < Riddl::Implementation #{{{
|
|
260
|
+
def response
|
|
261
|
+
Riddl::Parameter::Complex.new('overview','text/xml') do
|
|
262
|
+
<<-END
|
|
263
|
+
<overview xmlns='http://riddl.org/ns/common-patterns/notifications-producer/2.0'>
|
|
264
|
+
<topics/>
|
|
265
|
+
<subscriptions/>
|
|
266
|
+
</overview>
|
|
267
|
+
END
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end #}}}
|
|
271
|
+
|
|
272
|
+
class Topics < Riddl::Implementation #{{{
|
|
273
|
+
def response
|
|
274
|
+
opts = @a[0]
|
|
275
|
+
Riddl::Parameter::Complex.new('overview','text/xml') do
|
|
276
|
+
File.read(opts[:topics])
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end #}}}
|
|
280
|
+
|
|
281
|
+
class Subscriptions < Riddl::Implementation #{{{
|
|
282
|
+
def response
|
|
283
|
+
opts = @a[0]
|
|
284
|
+
Riddl::Parameter::Complex.new('subscriptions','text/xml') do
|
|
285
|
+
ret = XML::Smart::string <<-END
|
|
286
|
+
<subscriptions xmlns='http://riddl.org/ns/common-patterns/notifications-producer/2.0'/>
|
|
287
|
+
END
|
|
288
|
+
Dir.glob(File.join(opts[:notifications_dir],'*','subscription.xml')).each do |f|
|
|
289
|
+
ret.root.add('subscription').tap do |n|
|
|
290
|
+
n.attributes['id'] = File.basename(File.dirname(f))
|
|
291
|
+
XML::Smart.open_unprotected(f) do |doc|
|
|
292
|
+
n.attributes['url'] = doc.root.attributes['url']
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
ret.to_s
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end #}}}
|
|
300
|
+
|
|
301
|
+
class Subscription < Riddl::Implementation #{{{
|
|
302
|
+
def response
|
|
303
|
+
opts = @a[0]
|
|
304
|
+
id = @r[-1]
|
|
305
|
+
doc = XML::Smart::open_unprotected(File.join(opts[:notifications_dir],id,'subscription.xml'))
|
|
306
|
+
doc.root.attributes['id'] = id
|
|
307
|
+
Riddl::Parameter::Complex.new('subscriptions','text/xml',doc.to_s)
|
|
308
|
+
end
|
|
309
|
+
end #}}}
|
|
310
|
+
|
|
311
|
+
def self::implementation(opts)
|
|
312
|
+
opts[:log_dir] ||= File.expand_path(File.join(__dir__,'logs'))
|
|
313
|
+
opts[:notifications_dir] ||= File.expand_path(File.join(__dir__,'notifications'))
|
|
314
|
+
opts[:template] ||= File.expand_path(File.join(__dir__,'template.xes_yaml'))
|
|
315
|
+
opts[:topics] ||= File.expand_path(File.join(__dir__,'topics.xml'))
|
|
316
|
+
opts[:subscriptions] = {}
|
|
317
|
+
|
|
318
|
+
opts[:sse_keepalive_frequency] ||= 10
|
|
319
|
+
|
|
320
|
+
Dir.glob(File.join(opts[:notifications_dir],'*','subscription.xml')).each do |f|
|
|
321
|
+
XML::Smart::open_unprotected(f) do |doc|
|
|
322
|
+
doc.register_namespace :p, 'http://riddl.org/ns/common-patterns/notifications-producer/2.0'
|
|
323
|
+
doc.find('/p:subscription/p:topic').each do |t|
|
|
324
|
+
t.find('p:event').each do |e|
|
|
325
|
+
opts[:subscriptions][t.attributes['id']+'/'+e.text] ||= []
|
|
326
|
+
opts[:subscriptions][t.attributes['id']+'/'+e.text] << doc.root.attributes['url']
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
Proc.new do
|
|
333
|
+
interface 'access' do
|
|
334
|
+
on resource '[a-f0-9-]+.xes.yaml' do
|
|
335
|
+
run DownloadYAML, opts if get
|
|
336
|
+
end
|
|
337
|
+
on resource '[a-f0-9-]+.xes.xml' do
|
|
338
|
+
run DownloadXML, opts if get
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
interface 'events' do
|
|
342
|
+
run Handler, opts if post 'event'
|
|
343
|
+
end
|
|
344
|
+
interface 'notifications' do
|
|
345
|
+
on resource 'notifications' do
|
|
346
|
+
run Overview if get
|
|
347
|
+
on resource 'topics' do
|
|
348
|
+
run Topics, opts if get
|
|
349
|
+
end
|
|
350
|
+
on resource 'subscriptions' do
|
|
351
|
+
run Subscriptions, opts if get
|
|
352
|
+
run CreateSubscription, opts if post 'create_subscription'
|
|
353
|
+
on resource do
|
|
354
|
+
run Subscription, opts if get
|
|
355
|
+
run UpdateSubscription, opts if put 'change_subscription'
|
|
356
|
+
run DeleteSubscription, opts if delete
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
end
|
|
365
|
+
end
|
|
@@ -23,9 +23,15 @@
|
|
|
23
23
|
<interface name="notifications">
|
|
24
24
|
<xi:include href="http://www.riddl.org/ns/common-patterns/notifications-producer/2.0/producer.xml"/>
|
|
25
25
|
</interface>
|
|
26
|
+
<interface name="access">
|
|
27
|
+
<xi:include href="access.xml"/>
|
|
28
|
+
</interface>
|
|
26
29
|
|
|
27
30
|
<facade>
|
|
28
31
|
<tile>
|
|
32
|
+
<layer name="access">
|
|
33
|
+
<apply-to>/logs</apply-to>
|
|
34
|
+
</layer>
|
|
29
35
|
<layer name="events">
|
|
30
36
|
<apply-to>/</apply-to>
|
|
31
37
|
</layer>
|
|
@@ -114,53 +114,6 @@ module CPEE
|
|
|
114
114
|
end
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
-
def self::extract_probes(where,xml)
|
|
118
|
-
XML::Smart::string(xml) do |doc|
|
|
119
|
-
doc.register_namespace 'd', 'http://cpee.org/ns/description/1.0'
|
|
120
|
-
doc.find('//d:call | //d:manipulate').each do |c|
|
|
121
|
-
File.unlink(where + '_' + c.attributes['id'] + '.probe') rescue nil
|
|
122
|
-
c.find('d:annotations/d:_context_data_analysis/d:probes[d:probe]').each do |p|
|
|
123
|
-
File.write(where + '_' + c.attributes['id'] + '.probe', p.dump)
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def self::extract_annotations(where,xml)
|
|
130
|
-
ret = {}
|
|
131
|
-
XML::Smart::string(xml) do |doc|
|
|
132
|
-
doc.register_namespace 'd', 'http://cpee.org/ns/description/1.0'
|
|
133
|
-
doc.find('/d:description | //d:call | //d:manipulate').each do |c|
|
|
134
|
-
tid = c.attributes['id'] || 'complex'
|
|
135
|
-
fname = where + '_' + tid + '.anno'
|
|
136
|
-
nset = if tid == 'complex'
|
|
137
|
-
c.find('d:*[starts-with(name(),"_")]')
|
|
138
|
-
else
|
|
139
|
-
c.find('d:annotations')
|
|
140
|
-
end
|
|
141
|
-
nset.each do |p|
|
|
142
|
-
anno = p.dump
|
|
143
|
-
ret[tid] ||= []
|
|
144
|
-
ret[tid] << anno
|
|
145
|
-
end
|
|
146
|
-
if ret[tid]
|
|
147
|
-
if ret[tid].length > 1
|
|
148
|
-
ret[tid] = "<annotations xmlns=\"http://cpee.org/ns/description/1.0\">\n" +
|
|
149
|
-
ret[tid].join("\n") + "\n" +
|
|
150
|
-
'</annotations>'
|
|
151
|
-
else
|
|
152
|
-
ret[tid] = ret[tid][0]
|
|
153
|
-
end
|
|
154
|
-
hash = Digest::SHA1.hexdigest(ret[tid])
|
|
155
|
-
if !File.exist?(fname) || File.read(fname) != hash
|
|
156
|
-
File.write(fname,hash)
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
ret
|
|
162
|
-
end
|
|
163
|
-
|
|
164
117
|
def self::extract_result(result)
|
|
165
118
|
ret = result.map do |res|
|
|
166
119
|
if res['mimetype'].nil?
|
|
@@ -198,30 +151,6 @@ module CPEE
|
|
|
198
151
|
e.backtrace[0].gsub(/(\w+):(\d+):in.*/,'Probe ' + pid + ' Line \2: ') + e.message
|
|
199
152
|
end
|
|
200
153
|
|
|
201
|
-
def self::persist_values(where,values)
|
|
202
|
-
unless File.exist?(where)
|
|
203
|
-
File.write(where,'{}')
|
|
204
|
-
end
|
|
205
|
-
f = File.open(where,'r+')
|
|
206
|
-
f.flock(File::LOCK_EX)
|
|
207
|
-
json = JSON::load(f) || {}
|
|
208
|
-
json.merge!(values)
|
|
209
|
-
f.rewind
|
|
210
|
-
f.truncate(0)
|
|
211
|
-
f.write(JSON.generate(json))
|
|
212
|
-
f.flock(File::LOCK_UN)
|
|
213
|
-
f.close
|
|
214
|
-
end
|
|
215
|
-
def self::load_values(where)
|
|
216
|
-
ret = nil
|
|
217
|
-
File.open(where,'r') do |f|
|
|
218
|
-
f.flock(File::LOCK_SH)
|
|
219
|
-
ret = JSON::load(f)
|
|
220
|
-
f.flock(File::LOCK_UN)
|
|
221
|
-
end
|
|
222
|
-
ret
|
|
223
|
-
end
|
|
224
|
-
|
|
225
154
|
def self::forward(opts,topic,event_name,payload)
|
|
226
155
|
if topic == 'state' && event_name == 'change'
|
|
227
156
|
self::notify(opts,topic,event_name,payload)
|
|
@@ -246,29 +175,6 @@ module CPEE
|
|
|
246
175
|
parameters = content['parameters']
|
|
247
176
|
receiving = content['received']
|
|
248
177
|
|
|
249
|
-
if content['dslx']
|
|
250
|
-
CPEE::Logging::extract_probes(File.join(log_dir,instance),content['dslx'])
|
|
251
|
-
CPEE::Logging::extract_annotations(File.join(log_dir,instance),content['dslx']).each do |k,v|
|
|
252
|
-
so = JSON.parse(notification.to_json)
|
|
253
|
-
so['content'].delete('dslx')
|
|
254
|
-
so['content'].delete('dsl')
|
|
255
|
-
so['content'].delete('description')
|
|
256
|
-
so['content']['annotation'] = v
|
|
257
|
-
so['content']['activity'] = k
|
|
258
|
-
so['topic'] = 'annotation'
|
|
259
|
-
so['name'] = 'change'
|
|
260
|
-
EM.defer do
|
|
261
|
-
self::notify(opts,'annotation','change',so.to_json)
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
if topic == 'dataelements' && event_name == 'change'
|
|
267
|
-
if content['changed']&.any?
|
|
268
|
-
CPEE::Logging::persist_values(File.join(log_dir,instance + '.data.json'),content['values'])
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
|
|
272
178
|
event = {}
|
|
273
179
|
event['concept:instance'] = instancenr
|
|
274
180
|
event['concept:name'] = content['label'] if content['label']
|
|
@@ -298,90 +204,79 @@ module CPEE
|
|
|
298
204
|
unless parameters['arguments']&.nil?
|
|
299
205
|
event['data'] = parameters['arguments']
|
|
300
206
|
end if parameters
|
|
301
|
-
if content['
|
|
207
|
+
if (event_name == 'modify' || event_name == 'change') && content['values']&.any?
|
|
302
208
|
event['data'] = content['values'].map do |k,v|
|
|
303
209
|
{ 'name' => k, 'value' => v }
|
|
304
210
|
end
|
|
211
|
+
end
|
|
305
212
|
|
|
306
|
-
|
|
307
|
-
|
|
213
|
+
if topic == 'task' && event_name == 'probe'
|
|
214
|
+
rs = WEEL::ReadStructure.new(content['data'],{},{},{})
|
|
215
|
+
rc = CPEE::Logging::extract_result(receiving) if receiving && !receiving.empty?
|
|
216
|
+
te = event.dup
|
|
308
217
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if val != nil
|
|
320
|
-
event['stream:datastream'] ||= []
|
|
321
|
-
CPEE::Logging::merge_val(event['stream:datastream'],val,pid,source)
|
|
322
|
-
end
|
|
218
|
+
content['probes'].each do |p|
|
|
219
|
+
|
|
220
|
+
if p.dig('probe','extractor_type') == 'intrinsic'
|
|
221
|
+
pid = p.dig('probe','id')
|
|
222
|
+
source = p.dig('probe','source')
|
|
223
|
+
val = CPEE::Logging::extract_val(rs,p.dig('probe','extractor_code'),pid,nil) rescue nil
|
|
224
|
+
# Do not add datastream entries if the dataprobes return nil
|
|
225
|
+
if val != nil
|
|
226
|
+
event['stream:datastream'] ||= []
|
|
227
|
+
CPEE::Logging::merge_val(event['stream:datastream'],val,pid,source)
|
|
323
228
|
end
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
)
|
|
336
|
-
|
|
229
|
+
if event['stream:datastream'] && event['stream:datastream'].any?
|
|
230
|
+
EM.defer do
|
|
231
|
+
self::notify(
|
|
232
|
+
opts,
|
|
233
|
+
'stream',
|
|
234
|
+
'extraction',
|
|
235
|
+
notification.merge(
|
|
236
|
+
'topic'=>'stream',
|
|
237
|
+
'name'=>'extraction',
|
|
238
|
+
'datastream'=>event['stream:datastream']
|
|
239
|
+
).to_json
|
|
240
|
+
)
|
|
241
|
+
end
|
|
337
242
|
end
|
|
338
243
|
end
|
|
339
|
-
end
|
|
340
|
-
end
|
|
341
|
-
# Handle extrinsic data probes
|
|
342
|
-
if topic == 'activity' && event_name == 'receiving' && receiving && !receiving.empty?
|
|
343
|
-
fname = File.join(log_dir,instance + '_' + event['id:id'] + '.probe')
|
|
344
|
-
dname = File.join(log_dir,instance + '.data.json')
|
|
345
244
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
rc = CPEE::Logging::extract_result(receiving)
|
|
354
|
-
doc.find('//d:probe[d:extractor_type="extrinsic"]').each do |p|
|
|
355
|
-
pid = p.find('string(d:id)')
|
|
245
|
+
if receiving && !receiving.empty?
|
|
246
|
+
if p.dig('probe','extractor_type') == 'extrinsic'
|
|
247
|
+
pid = p.dig('probe','id')
|
|
248
|
+
source = p.dig('probe','source')
|
|
249
|
+
val = CPEE::Logging::extract_val(rs,p.dig('probe','extractor_code'),pid,rc) rescue nil
|
|
250
|
+
# Do not add datastream entries if the dataprobes return nil
|
|
251
|
+
if val != nil
|
|
356
252
|
te['stream:datastream'] ||= []
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
253
|
+
CPEE::Logging::merge_val(te['stream:datastream'],val,pid,source)
|
|
254
|
+
end
|
|
255
|
+
if te['stream:datastream'] && te['stream:datastream'].any?
|
|
256
|
+
te['cpee:lifecycle:transition'] = 'stream/data'
|
|
257
|
+
File.open(File.join(log_dir,instance+'.xes.yaml'),'a') do |f|
|
|
258
|
+
f << {'event' => te}.to_yaml
|
|
259
|
+
end
|
|
260
|
+
EM.defer do
|
|
261
|
+
self::notify(
|
|
262
|
+
opts,
|
|
263
|
+
'stream',
|
|
264
|
+
'extraction',
|
|
265
|
+
notification.merge(
|
|
266
|
+
'topic'=>'stream',
|
|
267
|
+
'name'=>'extraction',
|
|
268
|
+
'datastream'=>te['stream:datastream']
|
|
269
|
+
).to_json
|
|
270
|
+
)
|
|
361
271
|
end
|
|
362
272
|
end
|
|
363
273
|
end
|
|
364
274
|
end
|
|
365
|
-
|
|
366
|
-
te['cpee:lifecycle:transition'] = 'stream/data'
|
|
367
|
-
File.open(File.join(log_dir,instance+'.xes.yaml'),'a') do |f|
|
|
368
|
-
f << {'event' => te}.to_yaml
|
|
369
|
-
end
|
|
370
|
-
EM.defer do
|
|
371
|
-
self::notify(
|
|
372
|
-
opts,
|
|
373
|
-
'stream',
|
|
374
|
-
'extraction',
|
|
375
|
-
notification.merge(
|
|
376
|
-
'topic'=>'stream',
|
|
377
|
-
'name'=>'extraction',
|
|
378
|
-
'datastream'=>te['stream:datastream']
|
|
379
|
-
).to_json
|
|
380
|
-
)
|
|
381
|
-
end
|
|
382
|
-
end
|
|
275
|
+
|
|
383
276
|
end
|
|
277
|
+
|
|
384
278
|
end
|
|
279
|
+
|
|
385
280
|
if receiving && !receiving.empty?
|
|
386
281
|
event['data'] = receiving
|
|
387
282
|
end
|
data/server/log
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cpee-logging-xes-yaml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Juergen eTM Mangler
|
|
@@ -105,8 +105,9 @@ files:
|
|
|
105
105
|
- README.md
|
|
106
106
|
- Rakefile
|
|
107
107
|
- cpee-logging-xes-yaml.gemspec
|
|
108
|
-
- lib/cpee-logging-xes-yaml/
|
|
109
|
-
- lib/cpee-logging-xes-yaml/
|
|
108
|
+
- lib/cpee-logging-xes-yaml/access.xml
|
|
109
|
+
- lib/cpee-logging-xes-yaml/implementation.rb
|
|
110
|
+
- lib/cpee-logging-xes-yaml/implementation.xml
|
|
110
111
|
- lib/cpee-logging-xes-yaml/template.xes_yaml
|
|
111
112
|
- lib/cpee-logging-xes-yaml/tools.rb
|
|
112
113
|
- lib/cpee-logging-xes-yaml/topics.xml
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# This file is part of CPEE-LOGGING-XES-YAML.
|
|
2
|
-
#
|
|
3
|
-
# CPEE-LOGGING-XES-YAML is free software: you can redistribute it and/or modify it
|
|
4
|
-
# under the terms of the GNU Lesser General Public License as published by the Free
|
|
5
|
-
# Software Foundation, either version 3 of the License, or (at your option) any
|
|
6
|
-
# later version.
|
|
7
|
-
#
|
|
8
|
-
# CPEE-LOGGING-XES-YAML is distributed in the hope that it will be useful, but
|
|
9
|
-
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
10
|
-
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
|
11
|
-
# more details.
|
|
12
|
-
#
|
|
13
|
-
# You should have received a copy of the GNU Lesser General Public License along with
|
|
14
|
-
# CPEE-LOGGING-XES-YAML (file LICENSE in the main directory). If not, see
|
|
15
|
-
# <http://www.gnu.org/licenses/>.
|
|
16
|
-
|
|
17
|
-
require 'rubygems'
|
|
18
|
-
require 'redis'
|
|
19
|
-
require 'json'
|
|
20
|
-
require 'yaml'
|
|
21
|
-
require 'riddl/server'
|
|
22
|
-
require 'time'
|
|
23
|
-
|
|
24
|
-
require_relative 'tools'
|
|
25
|
-
|
|
26
|
-
module CPEE
|
|
27
|
-
module Logging
|
|
28
|
-
|
|
29
|
-
SERVER = File.expand_path(File.join(__dir__,'logging.xml'))
|
|
30
|
-
|
|
31
|
-
class Handler < Riddl::Implementation
|
|
32
|
-
def response
|
|
33
|
-
opts = @a[0]
|
|
34
|
-
type = @p[0].value
|
|
35
|
-
topic = @p[1].value
|
|
36
|
-
event_name = @p[2].value
|
|
37
|
-
payload = @p[3].value.read
|
|
38
|
-
|
|
39
|
-
### we do not write headers for now. Or else we can only do per instance sharding.
|
|
40
|
-
# unless File.exist? File.join(opts[:log_dir],@h['CPEE_INSTANCE_UUID'] + '.xes.yaml')
|
|
41
|
-
# notification = JSON.parse(payload)
|
|
42
|
-
# log = YAML::load(File.read(opts[:template]))
|
|
43
|
-
# log['log']['trace']['concept:name'] ||= notification['instance']
|
|
44
|
-
# log['log']['trace']['cpee:name'] ||= notification['instance-name'] if notification['instance-name']
|
|
45
|
-
# log['log']['trace']['cpee:instance'] ||= notification['instance-uuid']
|
|
46
|
-
# log['log']['trace']['cpee:parent_instance'] ||= notification.dig('content','attributes','parent_instance').to_i if notification.dig('content','attributes','parent_instance')
|
|
47
|
-
# log['log']['trace']['cpee:parent_instance_uuid'] ||= notification.dig('content','attributes','parent_instance_uuid') if notification.dig('content','attributes','parent_instance_uuid')
|
|
48
|
-
# log['log']['trace']['cpee:parent_instance_model'] ||= notification.dig('content','attributes','parent_instance_model') if notification.dig('content','attributes','parent_instance_model')
|
|
49
|
-
# log['log']['trace']['cpee:parent_instance_task_id'] ||= notification.dig('content','attributes','parent_instance_task_id') if notification.dig('content','attributes','parent_instance_task_id')
|
|
50
|
-
# log['log']['trace']['cpee:parent_instance_task_label'] ||= notification.dig('content','attributes','parent_instance_task_label') if notification.dig('content','attributes','parent_instance_task_label')
|
|
51
|
-
# File.open(File.join(opts[:log_dir],@h['CPEE_INSTANCE_UUID']+'.xes.yaml'),'w'){|f| f.puts log.to_yaml}
|
|
52
|
-
# end
|
|
53
|
-
|
|
54
|
-
EM.defer do
|
|
55
|
-
CPEE::Logging::forward opts, topic, event_name, payload
|
|
56
|
-
end if type == 'event'
|
|
57
|
-
EM.defer do
|
|
58
|
-
CPEE::Logging::doc opts, topic, event_name, payload
|
|
59
|
-
end
|
|
60
|
-
nil
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
class Overview < Riddl::Implementation #{{{
|
|
65
|
-
def response
|
|
66
|
-
Riddl::Parameter::Complex.new('overview','text/xml') do
|
|
67
|
-
<<-END
|
|
68
|
-
<overview xmlns='http://riddl.org/ns/common-patterns/notifications-producer/2.0'>
|
|
69
|
-
<topics/>
|
|
70
|
-
<subscriptions/>
|
|
71
|
-
</overview>
|
|
72
|
-
END
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end #}}}
|
|
76
|
-
|
|
77
|
-
class Topics < Riddl::Implementation #{{{
|
|
78
|
-
def response
|
|
79
|
-
opts = @a[0]
|
|
80
|
-
Riddl::Parameter::Complex.new("overview","text/xml") do
|
|
81
|
-
File.read(opts[:topics])
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end #}}}
|
|
85
|
-
|
|
86
|
-
class Subscriptions < Riddl::Implementation #{{{
|
|
87
|
-
def response
|
|
88
|
-
opts = @a[0]
|
|
89
|
-
Riddl::Parameter::Complex.new("subscriptions","text/xml") do
|
|
90
|
-
ret = XML::Smart::string <<-END
|
|
91
|
-
<subscriptions xmlns='http://riddl.org/ns/common-patterns/notifications-producer/2.0'/>
|
|
92
|
-
END
|
|
93
|
-
Dir.glob(File.join(opts[:notifications_dir],'*','subscription.xml')).each do |f|
|
|
94
|
-
ret.root.add('subscription').tap do |n|
|
|
95
|
-
n.attributes['id'] = File.basename(File.dirname(f))
|
|
96
|
-
XML::Smart.open_unprotected(f) do |doc|
|
|
97
|
-
n.attributes['url'] = doc.root.attributes['url']
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
ret.to_s
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end #}}}
|
|
105
|
-
|
|
106
|
-
class Subscription < Riddl::Implementation #{{{
|
|
107
|
-
def response
|
|
108
|
-
opts = @a[0]
|
|
109
|
-
id = @r[-1]
|
|
110
|
-
doc = XML::Smart::open_unprotected(File.join(opts[:notifications_dir],id,'subscription.xml'))
|
|
111
|
-
doc.root.attributes['id'] = id
|
|
112
|
-
Riddl::Parameter::Complex.new("subscriptions","text/xml",doc.to_s)
|
|
113
|
-
end
|
|
114
|
-
end #}}}
|
|
115
|
-
|
|
116
|
-
def self::implementation(opts)
|
|
117
|
-
opts[:log_dir] ||= File.expand_path(File.join(__dir__,'logs'))
|
|
118
|
-
opts[:notifications_dir] ||= File.expand_path(File.join(__dir__,'notifications'))
|
|
119
|
-
opts[:template] ||= File.expand_path(File.join(__dir__,'template.xes_yaml'))
|
|
120
|
-
opts[:topics] ||= File.expand_path(File.join(__dir__,'topics.xml'))
|
|
121
|
-
opts[:subscriptions] = {}
|
|
122
|
-
|
|
123
|
-
Dir.glob(File.join(opts[:notifications_dir],'*','subscription.xml')).each do |f|
|
|
124
|
-
XML::Smart::open_unprotected(f) do |doc|
|
|
125
|
-
doc.register_namespace :p, 'http://riddl.org/ns/common-patterns/notifications-producer/2.0'
|
|
126
|
-
doc.find('/p:subscription/p:topic').each do |t|
|
|
127
|
-
t.find('p:event').each do |e|
|
|
128
|
-
opts[:subscriptions][t.attributes['id']+'/'+e.text] ||= []
|
|
129
|
-
opts[:subscriptions][t.attributes['id']+'/'+e.text] << doc.root.attributes['url']
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
Proc.new do
|
|
136
|
-
interface 'events' do
|
|
137
|
-
run Handler, opts if post 'event'
|
|
138
|
-
end
|
|
139
|
-
interface 'notifications' do
|
|
140
|
-
on resource "notifications" do
|
|
141
|
-
run Overview if get
|
|
142
|
-
on resource "topics" do
|
|
143
|
-
run Topics, opts if get
|
|
144
|
-
end
|
|
145
|
-
on resource "subscriptions" do
|
|
146
|
-
run Subscriptions, opts if get
|
|
147
|
-
run CreateSubscription, opts if post 'create_subscription'
|
|
148
|
-
on resource do
|
|
149
|
-
run Subscription, opts if get
|
|
150
|
-
run UpdateSubscription, opts if put 'change_subscription'
|
|
151
|
-
run DeleteSubscription, opts if delete
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
end
|
|
160
|
-
end
|