infopark_reactor_migrations 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.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +165 -0
- data/README +64 -0
- data/Rakefile +19 -0
- data/infopark_reactor_migrations.gemspec +27 -0
- data/lib/generators/cm/migration/USAGE +8 -0
- data/lib/generators/cm/migration/migration_generator.rb +15 -0
- data/lib/generators/cm/migration/templates/template.rb +7 -0
- data/lib/infopark_reactor_migrations.rb +29 -0
- data/lib/reactor/cm/attribute.rb +84 -0
- data/lib/reactor/cm/bridge.rb +49 -0
- data/lib/reactor/cm/editorial_group.rb +22 -0
- data/lib/reactor/cm/group.rb +270 -0
- data/lib/reactor/cm/language.rb +56 -0
- data/lib/reactor/cm/link.rb +132 -0
- data/lib/reactor/cm/live_group.rb +22 -0
- data/lib/reactor/cm/missing_credentials.rb +7 -0
- data/lib/reactor/cm/obj.rb +402 -0
- data/lib/reactor/cm/obj_class.rb +186 -0
- data/lib/reactor/cm/object_base.rb +164 -0
- data/lib/reactor/cm/user.rb +100 -0
- data/lib/reactor/cm/workflow.rb +40 -0
- data/lib/reactor/cm/xml_attribute.rb +35 -0
- data/lib/reactor/cm/xml_markup.rb +85 -0
- data/lib/reactor/cm/xml_request.rb +82 -0
- data/lib/reactor/cm/xml_request_error.rb +16 -0
- data/lib/reactor/cm/xml_response.rb +41 -0
- data/lib/reactor/configuration.rb +7 -0
- data/lib/reactor/migration.rb +82 -0
- data/lib/reactor/migrations/railtie.rb +10 -0
- data/lib/reactor/migrations/version.rb +5 -0
- data/lib/reactor/plans/common_attribute.rb +32 -0
- data/lib/reactor/plans/common_group.rb +44 -0
- data/lib/reactor/plans/common_obj_class.rb +69 -0
- data/lib/reactor/plans/create_attribute.rb +32 -0
- data/lib/reactor/plans/create_group.rb +34 -0
- data/lib/reactor/plans/create_obj.rb +48 -0
- data/lib/reactor/plans/create_obj_class.rb +28 -0
- data/lib/reactor/plans/delete_attribute.rb +23 -0
- data/lib/reactor/plans/delete_group.rb +28 -0
- data/lib/reactor/plans/delete_obj.rb +22 -0
- data/lib/reactor/plans/delete_obj_class.rb +22 -0
- data/lib/reactor/plans/prepared.rb +15 -0
- data/lib/reactor/plans/rename_group.rb +32 -0
- data/lib/reactor/plans/rename_obj_class.rb +24 -0
- data/lib/reactor/plans/update_attribute.rb +23 -0
- data/lib/reactor/plans/update_group.rb +30 -0
- data/lib/reactor/plans/update_obj.rb +30 -0
- data/lib/reactor/plans/update_obj_class.rb +26 -0
- data/lib/reactor/tools/migrator.rb +135 -0
- data/lib/reactor/tools/response_handler/base.rb +22 -0
- data/lib/reactor/tools/response_handler/string.rb +19 -0
- data/lib/reactor/tools/response_handler/xml_attribute.rb +52 -0
- data/lib/reactor/tools/smart_xml_logger.rb +69 -0
- data/lib/reactor/tools/sower.rb +89 -0
- data/lib/reactor/tools/uploader.rb +131 -0
- data/lib/reactor/tools/versioner.rb +120 -0
- data/lib/reactor/tools/xml_attributes.rb +70 -0
- data/lib/tasks/cm_migrate.rake +8 -0
- data/lib/tasks/cm_seeds.rake +41 -0
- metadata +193 -0
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'reactor/tools/versioner'
|
2
|
+
|
3
|
+
module Reactor
|
4
|
+
# Class responsible for running a single migration, a helper for Migrator
|
5
|
+
class MigrationProxy
|
6
|
+
def initialize(versioner, name, version, direction, filename)
|
7
|
+
@versioner = versioner
|
8
|
+
@name = name
|
9
|
+
@version = version
|
10
|
+
@filename = filename
|
11
|
+
@direction = direction
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_migration
|
15
|
+
load @filename
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
return down if @direction.to_sym == :down
|
20
|
+
return up
|
21
|
+
end
|
22
|
+
|
23
|
+
def up
|
24
|
+
if @versioner.applied?(@version) then
|
25
|
+
puts "Migrating up: #{@name} (#{@filename}) already applied, skipping"
|
26
|
+
return true
|
27
|
+
else
|
28
|
+
result = class_name.send(:up) and @versioner.add(@version)
|
29
|
+
class_name.contained.each do |version|
|
30
|
+
puts "#{class_name.to_s} contains migration #{version}"
|
31
|
+
#@versioner.add(version) # not neccesary!
|
32
|
+
end if result
|
33
|
+
result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def down
|
38
|
+
result = class_name.send(:down) and @versioner.remove(@version)
|
39
|
+
class_name.contained.each do |version|
|
40
|
+
puts "#{class_name.to_s} contains migration #{version}"
|
41
|
+
@versioner.remove(version)
|
42
|
+
end if result
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
def class_name
|
47
|
+
return Kernel.const_get(@name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def name
|
51
|
+
@name
|
52
|
+
end
|
53
|
+
|
54
|
+
def version
|
55
|
+
@version
|
56
|
+
end
|
57
|
+
|
58
|
+
def filename
|
59
|
+
@filename
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Migrator is responsible for running migrations.
|
64
|
+
#
|
65
|
+
# <b>You should not use this class directly! Use rake cm:migrate instead.</b>
|
66
|
+
#
|
67
|
+
# Migrating to a specific version is possible by specifing VERSION environment
|
68
|
+
# variable: rake cm:migrate VERSION=0
|
69
|
+
# Depending on your current version migrations will be run up
|
70
|
+
# (target version > current version) or down (target version < current version)
|
71
|
+
#
|
72
|
+
# MIND THE FACT, that you land at the version <i>nearest</i> to target_version
|
73
|
+
# (possibly target version itself)
|
74
|
+
class Migrator
|
75
|
+
# Constructor takes two parameters migrations_path (relative path of migration files)
|
76
|
+
# and target_version (an integer or nil).
|
77
|
+
#
|
78
|
+
# Used by a rake task.
|
79
|
+
def initialize(migrations_path, target_version=nil)
|
80
|
+
@migrations_path = migrations_path
|
81
|
+
@target_version = target_version.to_i unless target_version.nil?
|
82
|
+
@target_version = 99999999999999 if target_version.nil?
|
83
|
+
@versioner = Versioner.instance
|
84
|
+
end
|
85
|
+
|
86
|
+
# Runs the migrations in proper direction (up or down)
|
87
|
+
# Ouputs current version when done
|
88
|
+
def migrate
|
89
|
+
return up if @target_version.to_i > current_version.to_i
|
90
|
+
return down
|
91
|
+
end
|
92
|
+
|
93
|
+
def up
|
94
|
+
rem_migrations = migrations.reject do |version, name, file|
|
95
|
+
version.to_i > @target_version.to_i or applied?(version)
|
96
|
+
end
|
97
|
+
run(rem_migrations, :up)
|
98
|
+
end
|
99
|
+
|
100
|
+
def down
|
101
|
+
rem_migrations = migrations.reject do |version, name, file|
|
102
|
+
version.to_i <= @target_version.to_i or not applied?(version)
|
103
|
+
end
|
104
|
+
run(rem_migrations.reverse, :down)
|
105
|
+
end
|
106
|
+
|
107
|
+
def migrations
|
108
|
+
files = Dir["#{@migrations_path}/[0-9]*_*.rb"].sort.collect do |file|
|
109
|
+
version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
|
110
|
+
[version, name, file]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def applied?(version)
|
115
|
+
@versioner.applied?(version)
|
116
|
+
end
|
117
|
+
|
118
|
+
def current_version
|
119
|
+
@versioner.current_version
|
120
|
+
end
|
121
|
+
|
122
|
+
def run(rem_migrations, direction)
|
123
|
+
begin
|
124
|
+
rem_migrations.each do |version, name, file|
|
125
|
+
migration = MigrationProxy.new(@versioner, name.camelize, version, direction, file)
|
126
|
+
puts "Migrating #{direction.to_s}: #{migration.name} (#{migration.filename})"
|
127
|
+
migration.load_migration and migration.run or raise "Migrating #{direction.to_s}: #{migration.name} (#{migration.filename}) failed"
|
128
|
+
end
|
129
|
+
ensure
|
130
|
+
puts "At version: " + @versioner.current_version.to_s
|
131
|
+
puts "WARNING: Could not store applied migrations!" if not @versioner.store
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Reactor
|
2
|
+
|
3
|
+
module ResponseHandler
|
4
|
+
|
5
|
+
# Common base class to handle a xml response. Provides helper methods to extract the content
|
6
|
+
# from a xml response.
|
7
|
+
class Base
|
8
|
+
|
9
|
+
attr_accessor :response
|
10
|
+
attr_accessor :context
|
11
|
+
|
12
|
+
# Common strategy method for each sub class.
|
13
|
+
def get(response, context)
|
14
|
+
@response = response
|
15
|
+
@context = context
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'reactor/tools/response_handler/base'
|
2
|
+
|
3
|
+
module Reactor
|
4
|
+
|
5
|
+
module ResponseHandler
|
6
|
+
|
7
|
+
class String < Base
|
8
|
+
|
9
|
+
def get(response, string)
|
10
|
+
super(response, string)
|
11
|
+
|
12
|
+
self.response.xpath(string)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'reactor/tools/response_handler/base'
|
2
|
+
|
3
|
+
module Reactor
|
4
|
+
|
5
|
+
module ResponseHandler
|
6
|
+
|
7
|
+
class XmlAttribute < Base
|
8
|
+
|
9
|
+
def get(response, attribute)
|
10
|
+
super(response, attribute)
|
11
|
+
|
12
|
+
name = attribute.name
|
13
|
+
type = attribute.type
|
14
|
+
|
15
|
+
method_name = "extract_#{type}"
|
16
|
+
|
17
|
+
self.send(method_name, name)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Extracts a string value with the given +name+ and returns a string.
|
23
|
+
def extract_string(name)
|
24
|
+
self.response.xpath("//#{name}/text()").to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
# Extracts a list value with the given +name+ and returns an array of strings.
|
28
|
+
def extract_list(name)
|
29
|
+
result = self.response.xpath("//#{name}/listitem/text()")
|
30
|
+
result = result.kind_of?(Array) ? result : [result]
|
31
|
+
|
32
|
+
result.map(&:to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
# This shit will break with the slightest change of the CM.
|
36
|
+
def extract_signaturelist(name)
|
37
|
+
signatures = []
|
38
|
+
self.response.xpath("//#{name}/").each do |potential_signature|
|
39
|
+
if (potential_signature.name.to_s == "listitem")
|
40
|
+
attribute = potential_signature.children.first.text.to_s
|
41
|
+
group = potential_signature.children.last.text.to_s
|
42
|
+
signatures << {:attribute => attribute, :group => group}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
signatures
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'term/ansicolor'
|
3
|
+
|
4
|
+
class SmartXmlLogger
|
5
|
+
|
6
|
+
include Term::ANSIColor
|
7
|
+
|
8
|
+
def initialize(forward_to, method = nil)
|
9
|
+
@logger = forward_to
|
10
|
+
@method = method
|
11
|
+
end
|
12
|
+
|
13
|
+
def configure(key, options)
|
14
|
+
@configuration ||= {}
|
15
|
+
@configuration[key] = options
|
16
|
+
end
|
17
|
+
|
18
|
+
def log(text)
|
19
|
+
return unless @logger
|
20
|
+
@logger.send(@method, text)
|
21
|
+
end
|
22
|
+
|
23
|
+
def log_xml(key, xml)
|
24
|
+
return unless @logger
|
25
|
+
|
26
|
+
options = @configuration[key]
|
27
|
+
|
28
|
+
dom = Nokogiri::XML::Document.parse(xml)
|
29
|
+
|
30
|
+
node_set = options[:xpath] ? dom.xpath(options[:xpath]) : dom
|
31
|
+
|
32
|
+
self.log(if node_set.respond_to?(:each)
|
33
|
+
node_set.map{|node| self.print_node(node, options[:start_indent] || 0)}.join
|
34
|
+
else
|
35
|
+
self.print_node(node_set, options[:start_indent] || 0)
|
36
|
+
end)
|
37
|
+
end
|
38
|
+
|
39
|
+
#private
|
40
|
+
|
41
|
+
def print_node(node, indent = 0)
|
42
|
+
return '' if node.text?
|
43
|
+
|
44
|
+
empty = node.children.empty?
|
45
|
+
has_text = node.children.detect{|child| child.text?}
|
46
|
+
|
47
|
+
out = ' ' * indent
|
48
|
+
|
49
|
+
attrs = node.attributes.values.map{|attr| %|#{attr.name}="#{red(attr.value)}"|}.join(' ')
|
50
|
+
attrs = " #{attrs}" if attrs.present?
|
51
|
+
|
52
|
+
out << "<#{green(node.name)}#{attrs}#{'/' if empty}>"
|
53
|
+
|
54
|
+
if has_text
|
55
|
+
out << "#{red(node.text)}"
|
56
|
+
else
|
57
|
+
out << "\n"
|
58
|
+
end
|
59
|
+
|
60
|
+
node.children.each do |child|
|
61
|
+
out << self.print_node(child, indent + 2)
|
62
|
+
end
|
63
|
+
|
64
|
+
out << ' ' * indent unless has_text || empty
|
65
|
+
out << "</#{green(node.name)}>\n" unless empty
|
66
|
+
out
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Reactor
|
2
|
+
|
3
|
+
class Sower
|
4
|
+
def initialize(filename)
|
5
|
+
@filename = filename
|
6
|
+
end
|
7
|
+
def sow
|
8
|
+
require @filename
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
class SeedObject < RailsConnector::Obj
|
15
|
+
end
|
16
|
+
|
17
|
+
module RailsConnector
|
18
|
+
class Obj
|
19
|
+
|
20
|
+
attr_accessor :keep_edited
|
21
|
+
|
22
|
+
def self.plant(path, &block)
|
23
|
+
obj = Obj.find_by_path(path)
|
24
|
+
raise ActiveRecord::RecordNotFound.new('plant: Ground not found:' +path) if obj.nil?
|
25
|
+
#obj.objClass = 'Container' # TODO: enable it!
|
26
|
+
#obj.save!
|
27
|
+
#obj.release!
|
28
|
+
obj.send(:reload_attributes)
|
29
|
+
obj.instance_eval(&block) if block_given?
|
30
|
+
# ActiveRecord is incompatible with changing the obj class, therefore you get RecordNotFound
|
31
|
+
begin
|
32
|
+
obj.save!
|
33
|
+
rescue ActiveRecord::RecordNotFound
|
34
|
+
end
|
35
|
+
obj.release unless obj.keep_edited || !Obj.last.edited?
|
36
|
+
obj
|
37
|
+
end
|
38
|
+
|
39
|
+
# creates of fetches an obj with given name (within context),
|
40
|
+
# executes a block on it (instance_eval)
|
41
|
+
# saves and releases (unless keep_edited = true was called)
|
42
|
+
# the object afterwards
|
43
|
+
def obj(name, objClass = 'Container', &block)
|
44
|
+
obj = Obj.find_by_path(File.join(self.path.to_s, name.to_s))
|
45
|
+
if obj.nil?
|
46
|
+
obj = Obj.create(:name => name, :parent => self.path, :obj_class => objClass)
|
47
|
+
else
|
48
|
+
obj = Obj.find_by_path(File.join(self.path.to_s, name.to_s))
|
49
|
+
if obj.obj_class != objClass
|
50
|
+
obj.obj_class = objClass
|
51
|
+
begin
|
52
|
+
obj.save!
|
53
|
+
rescue ActiveRecord::RecordNotFound
|
54
|
+
end
|
55
|
+
obj = Obj.find_by_path(File.join(self.path.to_s, name.to_s))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
obj.send(:reload_attributes, objClass)
|
59
|
+
obj.instance_eval(&block) if block_given?
|
60
|
+
obj.save!
|
61
|
+
obj.release unless obj.keep_edited || !Obj.last.edited?
|
62
|
+
obj
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.with(path, objClass = 'Container', &block)
|
66
|
+
splitted_path = path.split('/')
|
67
|
+
name = splitted_path.pop
|
68
|
+
# ensure path exists
|
69
|
+
(splitted_path.length).times do |i|
|
70
|
+
subpath = splitted_path[0,(i+1)].join('/').presence || '/'
|
71
|
+
subpath_parent = splitted_path[0,i].join('/').presence || '/'
|
72
|
+
subpath_name = splitted_path[i]
|
73
|
+
create(:name => subpath_name, :parent => subpath_parent, :obj_class => 'Container') unless Obj.find_by_path(subpath) unless subpath_name.blank?
|
74
|
+
end
|
75
|
+
parent_path = splitted_path.join('/').presence || '/'
|
76
|
+
parent = Obj.find_by_path(parent_path)
|
77
|
+
parent.obj(name, objClass, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
def do_not_release!
|
81
|
+
@keep_edited = true
|
82
|
+
end
|
83
|
+
|
84
|
+
def t(key, opts={})
|
85
|
+
I18n.t(key, opts)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Reactor
|
2
|
+
module Tools
|
3
|
+
class Uploader
|
4
|
+
|
5
|
+
attr_reader :cm_obj
|
6
|
+
|
7
|
+
def initialize(cm_obj)
|
8
|
+
self.cm_obj = cm_obj
|
9
|
+
end
|
10
|
+
|
11
|
+
# Uses streaming interface to upload data from
|
12
|
+
# given IO stream or memory location.
|
13
|
+
# Extension is used as basis for content detection.
|
14
|
+
# Larger file transfers should be executed through IO
|
15
|
+
# streams, which conserve memory.
|
16
|
+
#
|
17
|
+
# After the data has been successfuly transfered to
|
18
|
+
# streaming interface it stores contentType and resulting
|
19
|
+
# ticket into Reactor::Cm::Obj provided on initialization.
|
20
|
+
#
|
21
|
+
# NOTE: there is a known bug for Mac OS X: if you are
|
22
|
+
# uploading more files (IO objects) in sequence,
|
23
|
+
# the upload may fail randomly. For this platform
|
24
|
+
# and this case fallback to memory streaming is used.
|
25
|
+
def upload(data_or_io, extension)
|
26
|
+
if (data_or_io.kind_of?IO)
|
27
|
+
io = data_or_io
|
28
|
+
begin
|
29
|
+
ticket_id = stream_io(io, extension)
|
30
|
+
rescue Errno::EINVAL => e
|
31
|
+
if RUBY_PLATFORM.downcase.include?("darwin")
|
32
|
+
# mac os x is such a piece of shit
|
33
|
+
# writing to a socket can fail with EINVAL, randomly without
|
34
|
+
# visible reason when using body_stream
|
35
|
+
# in this case fallback to memory upload which always works (?!?!)
|
36
|
+
Reactor::Cm::LOGGER.log "MacOS X bug detected for #{io.inspect}"
|
37
|
+
io.rewind
|
38
|
+
return upload(io.read, extension)
|
39
|
+
else
|
40
|
+
raise e
|
41
|
+
end
|
42
|
+
end
|
43
|
+
else
|
44
|
+
ticket_id = stream_data(data_or_io, extension)
|
45
|
+
end
|
46
|
+
|
47
|
+
cm_obj.set(:contentType, extension)
|
48
|
+
cm_obj.set(:blob, {ticket_id=>{:encoding=>'stream'}})
|
49
|
+
|
50
|
+
ticket_id
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
attr_writer :cm_obj
|
56
|
+
|
57
|
+
# Stream into CM from memory. Used in cases when the file
|
58
|
+
# has already been read into memory
|
59
|
+
def stream_data(data, extension)
|
60
|
+
response, ticket_id = (Net::HTTP.new(self.class.streaming_host, self.class.streaming_port).post('/stream', data,
|
61
|
+
{'Content-Type' => self.class.content_type_for_ext(extension)}))
|
62
|
+
|
63
|
+
handle_response(response, ticket_id)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Stream directly an IO object into CM. Uses minimal memory,
|
67
|
+
# as the IO is read in 1024B-Blocks
|
68
|
+
def stream_io(io, extension)
|
69
|
+
request = Net::HTTP::Post.new('/stream')
|
70
|
+
request.body_stream = io
|
71
|
+
request.content_length = read_io_content_length(io)
|
72
|
+
request.content_type = self.class.content_type_for_ext(extension)
|
73
|
+
|
74
|
+
response, ticket_id = nil, nil
|
75
|
+
Net::HTTP.start(self.class.streaming_host, self.class.streaming_port) do |http|
|
76
|
+
http.read_timeout = 60
|
77
|
+
#http.set_debug_output $stderr
|
78
|
+
response, ticket_id = http.request(request)
|
79
|
+
end
|
80
|
+
|
81
|
+
handle_response(response, ticket_id)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns ticket_id if response if one of success (success or redirect)
|
85
|
+
def handle_response(response, ticket_id)
|
86
|
+
if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
|
87
|
+
ticket_id
|
88
|
+
else
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns the size of the IO stream.
|
94
|
+
# The underlying stream must support either
|
95
|
+
# the :stat method or be able to seek to
|
96
|
+
# random position
|
97
|
+
def read_io_content_length(io)
|
98
|
+
if (io.respond_to?(:stat))
|
99
|
+
# For files it is easy to read the filesize
|
100
|
+
return io.stat.size
|
101
|
+
else
|
102
|
+
# For streams it is not. We seek to end of
|
103
|
+
# the stream, read the position, and rewind
|
104
|
+
# to the previous location
|
105
|
+
old_pos = io.pos
|
106
|
+
io.seek(0, IO::SEEK_END)
|
107
|
+
content_length = io.pos
|
108
|
+
io.seek(old_pos, IO::SEEK_SET)
|
109
|
+
|
110
|
+
content_length
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.streaming_host
|
115
|
+
Reactor::Configuration.xml_access[:host]
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.streaming_port
|
119
|
+
Reactor::Configuration.xml_access[:port]
|
120
|
+
end
|
121
|
+
|
122
|
+
# It should theoretically return correct/matching
|
123
|
+
# mime type for given extension. But since the CM
|
124
|
+
# accepts 'application/octet-stream', no extra logic
|
125
|
+
# or external dependency is required.
|
126
|
+
def self.content_type_for_ext(extension)
|
127
|
+
'application/octet-stream'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'yaml'
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module Reactor
|
6
|
+
# Class responsible for interfacing with version-storing mechanism
|
7
|
+
class Versioner
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
# Slave class used by Versioner class to load and store migrated files
|
11
|
+
# inside the CM. It uses separate object type named "version_store"
|
12
|
+
# and stores data as base64'ed YAML inside recordSetCallback
|
13
|
+
# (Versionszuweisungsfunktion).
|
14
|
+
# Theoretically you could use any class for this purpose, but you would
|
15
|
+
# lose the ability to set recordSetCallback for this class. Other than
|
16
|
+
# that, it does not affect the object class in any way!
|
17
|
+
#
|
18
|
+
# Maybe the future version won't disrupt even this fuction.
|
19
|
+
class Slave
|
20
|
+
def name
|
21
|
+
"version_store"
|
22
|
+
end
|
23
|
+
|
24
|
+
def base_name
|
25
|
+
"objClass"
|
26
|
+
end
|
27
|
+
|
28
|
+
def exists?
|
29
|
+
begin
|
30
|
+
request = Reactor::Cm::XmlRequest.prepare do |xml|
|
31
|
+
xml.where_key_tag!(base_name, 'name', name)
|
32
|
+
xml.get_key_tag!(base_name, 'name')
|
33
|
+
end
|
34
|
+
response = request.execute!
|
35
|
+
return response.ok?
|
36
|
+
rescue
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def load
|
42
|
+
create if not exists?
|
43
|
+
request = Reactor::Cm::XmlRequest.prepare do |xml|
|
44
|
+
xml.where_key_tag!(base_name, 'name', name)
|
45
|
+
xml.get_key_tag!(base_name, 'recordSetCallback')
|
46
|
+
end
|
47
|
+
response = request.execute!
|
48
|
+
base64 = response.xpath("//recordSetCallback").text.to_s
|
49
|
+
yaml = Base64::decode64(base64)
|
50
|
+
data = YAML::load(yaml)
|
51
|
+
return [] if data.nil? or data == false
|
52
|
+
return data.to_a
|
53
|
+
end
|
54
|
+
|
55
|
+
def store(data)
|
56
|
+
create if not exists?
|
57
|
+
yaml = data.to_yaml
|
58
|
+
base64 = Base64::encode64(yaml).gsub("\n", '').gsub("\r", '')
|
59
|
+
content = '#' + base64
|
60
|
+
request = Reactor::Cm::XmlRequest.prepare do |xml|
|
61
|
+
xml.where_key_tag!(base_name, 'name', name)
|
62
|
+
xml.set_key_tag!(base_name, 'recordSetCallback', content)
|
63
|
+
end
|
64
|
+
response = request.execute!
|
65
|
+
response.ok?
|
66
|
+
end
|
67
|
+
|
68
|
+
def create
|
69
|
+
request = Reactor::Cm::XmlRequest.prepare do |xml|
|
70
|
+
xml.create_tag!(base_name) do
|
71
|
+
xml.tag!('name') do
|
72
|
+
xml.text!(name)
|
73
|
+
end
|
74
|
+
xml.tag!('objType') do
|
75
|
+
xml.text!('document')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
response = request.execute!
|
80
|
+
response.ok?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def initialize
|
85
|
+
@versions = []
|
86
|
+
@backend = Slave.new
|
87
|
+
load
|
88
|
+
end
|
89
|
+
|
90
|
+
def load
|
91
|
+
@versions = @backend.load
|
92
|
+
end
|
93
|
+
|
94
|
+
def store
|
95
|
+
@backend.store(@versions)
|
96
|
+
end
|
97
|
+
|
98
|
+
def applied?(version)
|
99
|
+
@versions.include? version.to_s
|
100
|
+
end
|
101
|
+
|
102
|
+
def add(version)
|
103
|
+
@versions << version.to_s
|
104
|
+
end
|
105
|
+
|
106
|
+
def remove(version)
|
107
|
+
not @versions.delete(version.to_s).nil?
|
108
|
+
end
|
109
|
+
|
110
|
+
def versions
|
111
|
+
@versions
|
112
|
+
end
|
113
|
+
|
114
|
+
def current_version
|
115
|
+
current = @versions.sort.reverse.first
|
116
|
+
return 0 if current.nil?
|
117
|
+
return current
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'reactor/cm/xml_attribute'
|
2
|
+
require 'reactor/tools/response_handler/xml_attribute'
|
3
|
+
|
4
|
+
module Reactor
|
5
|
+
|
6
|
+
module XmlAttributes
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
class_attribute :_attributes
|
12
|
+
self._attributes = {}
|
13
|
+
|
14
|
+
class_attribute :response_handler
|
15
|
+
self.response_handler = ResponseHandler::XmlAttribute.new
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
|
20
|
+
# This method can act as both getter and setter.
|
21
|
+
# I admit, that it is not the best design ever.
|
22
|
+
# But it makes a pretty good DSL
|
23
|
+
def primary_key(new_value = nil)
|
24
|
+
if new_value.nil?
|
25
|
+
@primary_key
|
26
|
+
else
|
27
|
+
@primary_key = new_value.to_s
|
28
|
+
@primary_key
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def attribute(name, options = {})
|
33
|
+
xml_name = options.delete(:name).presence || name
|
34
|
+
type = options.delete(:type).presence
|
35
|
+
|
36
|
+
attribute = Reactor::Cm::XmlAttribute.new(xml_name, type, options)
|
37
|
+
|
38
|
+
self._attributes[name.to_sym] = attribute
|
39
|
+
|
40
|
+
attr_accessor name
|
41
|
+
end
|
42
|
+
|
43
|
+
def attributes(scopes = [])
|
44
|
+
scopes = Array(scopes)
|
45
|
+
attributes = self._attributes
|
46
|
+
|
47
|
+
if scopes.present?
|
48
|
+
attributes.reject { |_, xml_attribute| (xml_attribute.scopes & scopes).blank? }
|
49
|
+
else
|
50
|
+
attributes
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def xml_attribute(name)
|
55
|
+
self._attributes[name.to_sym]
|
56
|
+
end
|
57
|
+
|
58
|
+
def xml_attribute_names
|
59
|
+
self._attributes.values.map(&:name)
|
60
|
+
end
|
61
|
+
|
62
|
+
def attribute_names
|
63
|
+
self._attributes.keys
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|