infopark_reactor_migrations 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|