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