lims-core 3.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.rvmrc +2 -0
- data/.travis.yml +2 -0
- data/.vimrc +27 -0
- data/.yard_templates/default/layout/html/footer.erb +0 -0
- data/.yardopts +1 -0
- data/Gemfile +54 -0
- data/Gemfile.lock +197 -0
- data/Guardfile +21 -0
- data/Guardfile.tmux +28 -0
- data/README.markdown +67 -0
- data/Rakefile +16 -0
- data/config/database.yml +16 -0
- data/doc/Array.html +116 -0
- data/doc/Array/ArrayLoggerPersistor.html +152 -0
- data/doc/Lims.html +114 -0
- data/doc/Lims/Core.html +178 -0
- data/doc/Lims/Core/Action.html +91 -0
- data/doc/Lims/Core/Actions.html +116 -0
- data/doc/Lims/Core/Actions/Action.html +216 -0
- data/doc/Lims/Core/Actions/Action/AfterEval.html +853 -0
- data/doc/Lims/Core/Actions/Action/InvalidParameters.html +268 -0
- data/doc/Lims/Core/Actions/ActionGroup.html +196 -0
- data/doc/Lims/Core/Actions/ActionGroup/AfterEval.html +315 -0
- data/doc/Lims/Core/Actions/BulkAction.html +224 -0
- data/doc/Lims/Core/Actions/BulkAction/AfterEval.html +253 -0
- data/doc/Lims/Core/Actions/TestActionGroup.html +101 -0
- data/doc/Lims/Core/Actions/TestActionGroup/Action.html +133 -0
- data/doc/Lims/Core/Actions/TestActionGroup/ActionGroup.html +127 -0
- data/doc/Lims/Core/Base.html +287 -0
- data/doc/Lims/Core/Base/AccessibleViaSuper.html +252 -0
- data/doc/Lims/Core/Base/ClassMethod.html +177 -0
- data/doc/Lims/Core/Base/HashString.html +177 -0
- data/doc/Lims/Core/Base/IsArrayOf.html +606 -0
- data/doc/Lims/Core/Base/State.html +130 -0
- data/doc/Lims/Core/Organization.html +113 -0
- data/doc/Lims/Core/Organization/Batch.html +106 -0
- data/doc/Lims/Core/Persistence.html +267 -0
- data/doc/Lims/Core/Persistence/ComparisonFilter.html +318 -0
- data/doc/Lims/Core/Persistence/Filter.html +252 -0
- data/doc/Lims/Core/Persistence/IdentityMap.html +409 -0
- data/doc/Lims/Core/Persistence/IdentityMap/Class.html +144 -0
- data/doc/Lims/Core/Persistence/IdentityMap/DuplicateError.html +126 -0
- data/doc/Lims/Core/Persistence/IdentityMap/DuplicateIdError.html +136 -0
- data/doc/Lims/Core/Persistence/IdentityMap/DuplicateObjectError.html +136 -0
- data/doc/Lims/Core/Persistence/IdentityMapClass.html +133 -0
- data/doc/Lims/Core/Persistence/Logger.html +105 -0
- data/doc/Lims/Core/Persistence/Logger/Persistor.html +334 -0
- data/doc/Lims/Core/Persistence/Logger/Session.html +452 -0
- data/doc/Lims/Core/Persistence/Logger/Store.html +470 -0
- data/doc/Lims/Core/Persistence/MessageBus.html +871 -0
- data/doc/Lims/Core/Persistence/MessageBus/ConnectionError.html +123 -0
- data/doc/Lims/Core/Persistence/MessageBus/InvalidSettingsError.html +122 -0
- data/doc/Lims/Core/Persistence/MultiCriteriaFilter.html +293 -0
- data/doc/Lims/Core/Persistence/PersistAssociationTrait.html +91 -0
- data/doc/Lims/Core/Persistence/PersistableTrait.html +91 -0
- data/doc/Lims/Core/Persistence/Persistor.html +3072 -0
- data/doc/Lims/Core/Persistence/Persistor/DuplicateError.html +205 -0
- data/doc/Lims/Core/Persistence/Persistor/DuplicateIdError.html +147 -0
- data/doc/Lims/Core/Persistence/Persistor/DuplicateObjectError.html +147 -0
- data/doc/Lims/Core/Persistence/PersistorTrait.html +91 -0
- data/doc/Lims/Core/Persistence/ResourceState.html +1738 -0
- data/doc/Lims/Core/Persistence/Search.html +269 -0
- data/doc/Lims/Core/Persistence/Search/CreateSearch.html +251 -0
- data/doc/Lims/Core/Persistence/Search/SearchPersistor.html +240 -0
- data/doc/Lims/Core/Persistence/Search/SearchSequelPersistor.html +396 -0
- data/doc/Lims/Core/Persistence/Sequel.html +117 -0
- data/doc/Lims/Core/Persistence/Sequel/Filters.html +462 -0
- data/doc/Lims/Core/Persistence/Sequel/ForTest.html +101 -0
- data/doc/Lims/Core/Persistence/Sequel/ForTest/Name.html +137 -0
- data/doc/Lims/Core/Persistence/Sequel/ForTest/Name/NamePersitor.html +143 -0
- data/doc/Lims/Core/Persistence/Sequel/Migrations.html +266 -0
- data/doc/Lims/Core/Persistence/Sequel/Persistor.html +665 -0
- data/doc/Lims/Core/Persistence/Sequel/Session.html +501 -0
- data/doc/Lims/Core/Persistence/Sequel/Store.html +417 -0
- data/doc/Lims/Core/Persistence/Session.html +2751 -0
- data/doc/Lims/Core/Persistence/Session/UnmanagedObjectError.html +111 -0
- data/doc/Lims/Core/Persistence/StateGroup.html +696 -0
- data/doc/Lims/Core/Persistence/StateList.html +498 -0
- data/doc/Lims/Core/Persistence/Store.html +695 -0
- data/doc/Lims/Core/Persistence/UuidResource.html +1044 -0
- data/doc/Lims/Core/Persistence/UuidResource/InvalidUuidError.html +111 -0
- data/doc/Lims/Core/Persistence/UuidResource/UuidResourcePersistor.html +337 -0
- data/doc/Lims/Core/Persistence/Uuidable.html +799 -0
- data/doc/Lims/Core/Persistor.html +320 -0
- data/doc/Lims/Core/Resource.html +165 -0
- data/doc/Object.html +228 -0
- data/doc/SessionSpec.html +101 -0
- data/doc/SessionSpec/Model.html +279 -0
- data/doc/SessionSpec/Model/ModelPersistor.html +327 -0
- data/doc/_index.html +732 -0
- data/doc/class_list.html +47 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +55 -0
- data/doc/css/style.css +322 -0
- data/doc/file.README.html +127 -0
- data/doc/file_list.html +49 -0
- data/doc/frames.html +13 -0
- data/doc/index.html +127 -0
- data/doc/js/app.js +205 -0
- data/doc/js/full_list.js +167 -0
- data/doc/js/jquery.js +16 -0
- data/doc/method_list.html +1894 -0
- data/doc/top-level-namespace.html +100 -0
- data/lib/common.rb +18 -0
- data/lib/lims-core.rb +29 -0
- data/lib/lims-core/actions.rb +10 -0
- data/lib/lims-core/actions/action.rb +185 -0
- data/lib/lims-core/actions/action_group.rb +54 -0
- data/lib/lims-core/actions/bulk_action.rb +65 -0
- data/lib/lims-core/base.rb +132 -0
- data/lib/lims-core/helpers.rb +41 -0
- data/lib/lims-core/persistence.rb +15 -0
- data/lib/lims-core/persistence/comparison_filter.rb +54 -0
- data/lib/lims-core/persistence/filter.rb +23 -0
- data/lib/lims-core/persistence/identity_map.rb +55 -0
- data/lib/lims-core/persistence/logger/all.rb +5 -0
- data/lib/lims-core/persistence/logger/persistor.rb +35 -0
- data/lib/lims-core/persistence/logger/session.rb +30 -0
- data/lib/lims-core/persistence/logger/store.rb +37 -0
- data/lib/lims-core/persistence/message_bus.rb +131 -0
- data/lib/lims-core/persistence/multi_criteria_filter.rb +50 -0
- data/lib/lims-core/persistence/persist_association_trait.rb +96 -0
- data/lib/lims-core/persistence/persistable_trait.rb +150 -0
- data/lib/lims-core/persistence/persistor.rb +495 -0
- data/lib/lims-core/persistence/resource_state.rb +157 -0
- data/lib/lims-core/persistence/search.rb +3 -0
- data/lib/lims-core/persistence/search/all.rb +3 -0
- data/lib/lims-core/persistence/search/create_search.rb +55 -0
- data/lib/lims-core/persistence/search/search_persistor.rb +45 -0
- data/lib/lims-core/persistence/search/search_sequel_persistor.rb +40 -0
- data/lib/lims-core/persistence/sequel.rb +14 -0
- data/lib/lims-core/persistence/sequel/filters.rb +106 -0
- data/lib/lims-core/persistence/sequel/migrations.rb +14 -0
- data/lib/lims-core/persistence/sequel/migrations/add_audit_tables.rb +147 -0
- data/lib/lims-core/persistence/sequel/migrations/initial.rb +156 -0
- data/lib/lims-core/persistence/sequel/persistor.rb +200 -0
- data/lib/lims-core/persistence/sequel/session.rb +136 -0
- data/lib/lims-core/persistence/sequel/store.rb +37 -0
- data/lib/lims-core/persistence/session.rb +409 -0
- data/lib/lims-core/persistence/state_group.rb +97 -0
- data/lib/lims-core/persistence/state_list.rb +56 -0
- data/lib/lims-core/persistence/store.rb +73 -0
- data/lib/lims-core/persistence/uuid_resource.rb +115 -0
- data/lib/lims-core/persistence/uuid_resource_persistor.rb +43 -0
- data/lib/lims-core/persistence/uuidable.rb +107 -0
- data/lib/lims-core/resource.rb +21 -0
- data/lib/lims-core/subclass_tracker.rb +30 -0
- data/lib/lims-core/version.rb +5 -0
- data/lims-core.gemspec +40 -0
- data/makefile +52 -0
- data/showoff/core-2012-06-11/core/01_slide.md +237 -0
- data/showoff/core-2012-06-11/core/02_slide.md +110 -0
- data/showoff/core-2012-06-11/custom.css +44 -0
- data/showoff/core-2012-06-11/main/01_slide.md +53 -0
- data/showoff/core-2012-06-11/showoff.json +10 -0
- data/showoff/core-2012-06-11/tp1.tpl +1 -0
- data/spec/actions/action_group_spec.rb +39 -0
- data/spec/actions/spec_helper.rb +1 -0
- data/spec/persistence/identity_map_spec.rb +55 -0
- data/spec/persistence/logger/spec_helper.rb +7 -0
- data/spec/persistence/logger/store_spec.rb +48 -0
- data/spec/persistence/message_bus_spec.rb +76 -0
- data/spec/persistence/sequel/session_spec.rb +125 -0
- data/spec/persistence/sequel/spec_helper.rb +39 -0
- data/spec/persistence/sequel/store_shared.rb +25 -0
- data/spec/persistence/sequel/store_spec.rb +22 -0
- data/spec/persistence/session_spec.rb +199 -0
- data/spec/persistence/spec_helper.rb +2 -0
- data/spec/persistence/uuid_resource_spec.rb +80 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/subclass_tracker_sperc.rb +62 -0
- data/utils/constant_tree.rb +29 -0
- data/utils/stack.rb +48 -0
- metadata +402 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
# vi: ts=2 sts=2 et sw=2 spell spelllang=en
|
2
|
+
require 'common'
|
3
|
+
|
4
|
+
module Lims::Core
|
5
|
+
module Base
|
6
|
+
def self.included(klass)
|
7
|
+
klass.class_eval do
|
8
|
+
include Virtus
|
9
|
+
include Aequitas
|
10
|
+
include AccessibleViaSuper
|
11
|
+
extend Forwardable
|
12
|
+
extend ClassMethod
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module AccessibleViaSuper
|
17
|
+
def initialize(*args, &block)
|
18
|
+
# readonly attributes are normaly not allowed in constructor
|
19
|
+
# by Virtus. We need to call set_attributes explicitely
|
20
|
+
options = args.extract_options!
|
21
|
+
# we would use `options & [:row ... ]` if we could
|
22
|
+
# but Sequel redefine Hash#& ...
|
23
|
+
initializables = self.class.attributes.select {|a| a.options[:initializable] == true }
|
24
|
+
initial_options = options.subset(initializables.map(&:name))
|
25
|
+
set_attributes(initial_options)
|
26
|
+
super(*args, options - initial_options, &block).tap {
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Like attributes but allow class to define a version
|
31
|
+
# without side effet.
|
32
|
+
# For example attribute on a UuidResource will generate an uuid.
|
33
|
+
# This is the method called by dirty_key_for
|
34
|
+
def attributes_for_dirty
|
35
|
+
attributes
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
# Compare 2 resources.
|
40
|
+
# They are == if they have the same values (attributes),
|
41
|
+
# regardless they are the same ruby object or not.
|
42
|
+
# @param other
|
43
|
+
# @return [Boolean]
|
44
|
+
def ==(other)
|
45
|
+
self.attributes == (other.respond(:attributes) || {} )
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
module ClassMethod
|
50
|
+
def is_array_of(child_klass, options = {}, &initializer)
|
51
|
+
define_method :initialize_array do |*args|
|
52
|
+
@content = initializer ? initializer[self, child_klass] : []
|
53
|
+
end
|
54
|
+
|
55
|
+
class_eval do
|
56
|
+
include Enumerable
|
57
|
+
include IsArrayOf
|
58
|
+
def_delegators :@content, :each, :size , :each_with_index, :map, :zip, :clear, :empty?, :to_s \
|
59
|
+
, :include?, :to_a, :first, :last
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
module IsArrayOf
|
68
|
+
|
69
|
+
def initialize(*args, &block)
|
70
|
+
super(*args, &block)
|
71
|
+
initialize_array()
|
72
|
+
end
|
73
|
+
|
74
|
+
# Add content to compare
|
75
|
+
# If classe are not in the same hierarchy we only compare the content
|
76
|
+
# @param other to compare with
|
77
|
+
# @return [Boolean]
|
78
|
+
def ==(other)
|
79
|
+
if other.is_a?(self.class) || self.is_a?(other.class)
|
80
|
+
super(other)
|
81
|
+
else
|
82
|
+
!other.nil?
|
83
|
+
end && self.to_a == other.to_a
|
84
|
+
end
|
85
|
+
|
86
|
+
# The underlying array. Use to everything which is not directly delegated
|
87
|
+
# @return [Array]
|
88
|
+
def content
|
89
|
+
@content
|
90
|
+
end
|
91
|
+
|
92
|
+
# Delegate [] to the underlying array.
|
93
|
+
# This is needed because Virtus redefine [] as well
|
94
|
+
# @param [Fixnum, ... ] i index
|
95
|
+
# @return [Object]
|
96
|
+
def [](i)
|
97
|
+
case i
|
98
|
+
when Fixnum then self.content[i]
|
99
|
+
else super(i)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def []=(i, value)
|
104
|
+
case i
|
105
|
+
when Fixnum then self.content[i]=value
|
106
|
+
else super(i, value)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
# iterate only between non empty lanes.
|
110
|
+
# @yield [content]
|
111
|
+
# @return itself
|
112
|
+
def each_content
|
113
|
+
@content.each do |content|
|
114
|
+
yield content if content
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class HashString < Virtus::Attribute::Object
|
120
|
+
primitive Hash
|
121
|
+
def coerce(hash)
|
122
|
+
hash.rekey {|key| key.to_s }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# @todo override state_machine to automatically add
|
127
|
+
# attribute
|
128
|
+
class State < Virtus::Attribute::Object
|
129
|
+
primitive String
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Lims::Core
|
2
|
+
module Helpers
|
3
|
+
def self.gem_available?(gem_name)
|
4
|
+
begin
|
5
|
+
Gem::Specification.find_by_name(gem_name)
|
6
|
+
rescue Gem::LoadError
|
7
|
+
false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Load the available gem for json
|
12
|
+
if gem_available?('jrjackson')
|
13
|
+
require 'jrjackson'
|
14
|
+
def self.to_json(object)
|
15
|
+
JrJackson::Json.dump(object)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.load_json(json)
|
19
|
+
JrJackson::Json.load(json)
|
20
|
+
end
|
21
|
+
elsif gem_available?('oj')
|
22
|
+
require 'oj'
|
23
|
+
def self.to_json(object)
|
24
|
+
Oj.dump(object, :mode => :compat)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.load_json(json)
|
28
|
+
Oj.load(json)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
require 'json'
|
32
|
+
def self.to_json(object)
|
33
|
+
object.to_json
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.load_json(json)
|
37
|
+
JSON.parse(json)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#vi: ts=2 sw=2 et
|
2
|
+
require 'common'
|
3
|
+
require 'facets/string'
|
4
|
+
|
5
|
+
require 'lims-core/persistence/session'
|
6
|
+
|
7
|
+
module Lims::Core
|
8
|
+
# Generic persistence layer.
|
9
|
+
# The main objects are {Persistence::Session Session} which
|
10
|
+
# is in charge of saving and restoring object and {Persistence::Store} via Persistors.
|
11
|
+
# Persistors are mixins specific to each persistence types.
|
12
|
+
# For example, see the {Sequel::Persistor}.
|
13
|
+
module Persistence
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'lims-core/persistence/filter'
|
2
|
+
require 'lims-core/resource'
|
3
|
+
|
4
|
+
|
5
|
+
module Lims::Core
|
6
|
+
module Persistence
|
7
|
+
# Filter performing a comparison between the resource field's value
|
8
|
+
# and a given value.
|
9
|
+
# Key being the name of the resource's field and the value is a Hash.
|
10
|
+
# The key of the hash is a comparison operator
|
11
|
+
# and the value is the given value the filter do the comparison against.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
#
|
15
|
+
# "model": "kit",
|
16
|
+
# "criteria": {
|
17
|
+
# "comparison": {
|
18
|
+
# "expires": {
|
19
|
+
# ">=": "2013-04-24"
|
20
|
+
# }
|
21
|
+
# }
|
22
|
+
# }
|
23
|
+
#
|
24
|
+
# Will look for all kits expires after the given date ("2013-04-24").
|
25
|
+
#
|
26
|
+
class ComparisonFilter < Filter
|
27
|
+
include Resource
|
28
|
+
|
29
|
+
NOT_IN_ROOT = 1
|
30
|
+
|
31
|
+
attribute :criteria, Hash, :required => true
|
32
|
+
|
33
|
+
# For Sequel, keys needs to be a Symbol to be seen as column.
|
34
|
+
# String are seen as 'value'
|
35
|
+
def initialize(criteria)
|
36
|
+
criteria = { :criteria => criteria } unless criteria.include?(:criteria)
|
37
|
+
criteria[:criteria].rekey!{ |k| k.to_sym }
|
38
|
+
super(criteria)
|
39
|
+
end
|
40
|
+
|
41
|
+
def call(persistor)
|
42
|
+
persistor.comparison_filter(criteria[:comparison])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Persistor
|
48
|
+
# @param [Hash] criteria a
|
49
|
+
# @return [Persistor]
|
50
|
+
def comparison_filter(criteria)
|
51
|
+
raise NotImplementedError, "comparison_filter methods needs to be implemented for subclass of Persistor"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#vi: ts=2 sw=2 et
|
2
|
+
require 'common'
|
3
|
+
|
4
|
+
module Lims::Core
|
5
|
+
module Persistence
|
6
|
+
# @abstract Base class of all filters.
|
7
|
+
# A filter acts on persistors and can be chained.
|
8
|
+
# Note: This class is not really usefull in a *Ruby world* and is mainly
|
9
|
+
# here for documentation.
|
10
|
+
class Filter
|
11
|
+
# Transform a persistor to a "filtered persistor"
|
12
|
+
# The filtered persistor loading only the filtered object.
|
13
|
+
# Note that the actual implementation of the filter depends on the
|
14
|
+
# *type* of the persistor (Sequel for example).
|
15
|
+
# @param persistor [Persistence::Persistor]
|
16
|
+
# @return [Persistor]
|
17
|
+
def call(persistor)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
2
|
+
|
3
|
+
require 'common'
|
4
|
+
|
5
|
+
module Lims::Core
|
6
|
+
module Persistence
|
7
|
+
# Mixing giving an identity map behavior
|
8
|
+
# ie a map (both way) between id and object
|
9
|
+
module IdentityMap
|
10
|
+
# Raised if there is any duplicate in the identity map
|
11
|
+
class DuplicateError < RuntimeError
|
12
|
+
end
|
13
|
+
|
14
|
+
#Raised if the `id` is already associated to a different `object`
|
15
|
+
class DuplicateIdError <DuplicateError
|
16
|
+
end
|
17
|
+
|
18
|
+
#Raised if the `object` is already associated to a different `id`
|
19
|
+
class DuplicateObjectError < DuplicateError
|
20
|
+
end
|
21
|
+
|
22
|
+
# Look for the id associated to an object and yield it to the block
|
23
|
+
# if found.
|
24
|
+
def id_for(object, &block)
|
25
|
+
@object_to_id[object].andtap(&block)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Look for the object associated to an object and yield it to the block
|
29
|
+
# if found.
|
30
|
+
def object_for(id, &block)
|
31
|
+
@id_to_object[id].andtap(&block)
|
32
|
+
end
|
33
|
+
|
34
|
+
# bound an id to an object
|
35
|
+
def map_id_object(id, object)
|
36
|
+
return nil unless id && object
|
37
|
+
raise DuplicateIdError, id unless @id_to_object.fetch(id, object).equal? object
|
38
|
+
raise DuplicateObjectError, object unless @object_to_id.fetch(object, id).equal? id
|
39
|
+
@id_to_object[id] = object
|
40
|
+
@object_to_id[object] = id
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(*args, &block)
|
44
|
+
super(*args, &block)
|
45
|
+
@id_to_object = {}
|
46
|
+
@object_to_id = {}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Class version
|
50
|
+
class Class
|
51
|
+
include IdentityMap
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
2
|
+
|
3
|
+
|
4
|
+
module Lims::Core
|
5
|
+
module Persistence
|
6
|
+
module Logger
|
7
|
+
# Mixin giving extended the persistor classes with
|
8
|
+
# the Logger (save) behavior.
|
9
|
+
module Persistor
|
10
|
+
private
|
11
|
+
|
12
|
+
# Load an object to the underlying logger
|
13
|
+
# @param [Resource] object the object
|
14
|
+
# @return the Id if save successful
|
15
|
+
def save_raw(object, *params)
|
16
|
+
case object
|
17
|
+
when Resource then @session.log("#{object.class.name}: #{filter_attributes_on_save(object.attributes)}")
|
18
|
+
else
|
19
|
+
@session.log("#{object.inspect}")
|
20
|
+
end
|
21
|
+
object
|
22
|
+
end
|
23
|
+
|
24
|
+
def save_as_aggregation(source_id, target, *params)
|
25
|
+
@session.with_indent("#{params} - ") do
|
26
|
+
super(source_id, target)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def save_raw_association(source_id, target_id, *params)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'lims-core/persistence/session'
|
5
|
+
|
6
|
+
module Lims::Core
|
7
|
+
module Persistence
|
8
|
+
module Logger
|
9
|
+
# Logger specific implementation of a {Persistence::Session Session}.
|
10
|
+
class Session < Persistence::Session
|
11
|
+
|
12
|
+
attr_reader :indent_level
|
13
|
+
def initialize(*args, &block)
|
14
|
+
@indent_level = ""
|
15
|
+
super(*args, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def log(msg)
|
19
|
+
@store.log(indent_level+msg)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Execute a block with the specified indent level indicator.
|
23
|
+
# @param [String] indent the indent level indicator
|
24
|
+
def with_indent(indent=" - ", &block)
|
25
|
+
temporarily('@indent_level' => @indent_level+indent, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'lims-core/persistence'
|
5
|
+
require 'lims-core/persistence/store'
|
6
|
+
require 'lims-core/persistence/logger/session'
|
7
|
+
require 'lims-core/persistence/logger/persistor'
|
8
|
+
|
9
|
+
module Lims::Core
|
10
|
+
module Persistence
|
11
|
+
module Logger
|
12
|
+
# An Logger::Store, a store 'logging' object instead of
|
13
|
+
# saving them.
|
14
|
+
class Store < Persistence::Store
|
15
|
+
attr_reader :logger
|
16
|
+
attr_reader :method
|
17
|
+
|
18
|
+
# Create a store with an underlying logger.
|
19
|
+
# @param [Logger, file] logger
|
20
|
+
# @param [Symbol, String] method the method call to
|
21
|
+
# send information to the logger.
|
22
|
+
def initialize(logger, method=:info, *args)
|
23
|
+
@logger = case logger
|
24
|
+
when ::Logger then logger
|
25
|
+
else ::Logger.new(logger)
|
26
|
+
end
|
27
|
+
@method = method
|
28
|
+
super(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def log(msg)
|
32
|
+
@logger.send(@method, msg)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
require 'common'
|
3
|
+
|
4
|
+
module Lims
|
5
|
+
module Core
|
6
|
+
module Persistence
|
7
|
+
|
8
|
+
# Basic methods to publish messages on the bus
|
9
|
+
# Use the bunny gem as RabbitMQ client
|
10
|
+
class MessageBus
|
11
|
+
|
12
|
+
include Virtus
|
13
|
+
include Aequitas
|
14
|
+
attribute :connection_uri, String, :required => true, :writer => :private
|
15
|
+
attribute :exchange_name, String, :required => true, :writer => :private
|
16
|
+
# By default, exchange_type is topic
|
17
|
+
attribute :exchange_type, String, :required => true, :writer => :private
|
18
|
+
attribute :durable, Boolean, :required => true, :writer => :private
|
19
|
+
attribute :prefetch_number, Integer, :required => true, :writer => :private
|
20
|
+
attribute :heart_beat, Integer, :required => false, :writer => :private
|
21
|
+
attribute :backend_application_id, String, :required => true
|
22
|
+
|
23
|
+
# Exception ConnectionError raised after a failed connection
|
24
|
+
# to RabbitMQ server.
|
25
|
+
class ConnectionError < StandardError
|
26
|
+
end
|
27
|
+
|
28
|
+
# Exception InvalidSettingsError raised after a setting error
|
29
|
+
class InvalidSettingsError < StandardError
|
30
|
+
end
|
31
|
+
|
32
|
+
# Initialize the message bus and check the required options
|
33
|
+
# are passed as parameters.
|
34
|
+
# @param [Hash] settings
|
35
|
+
def initialize(settings = {})
|
36
|
+
raise InvalidSettingsError, "MessageBug settings are empty" if settings == nil
|
37
|
+
@heart_beat = settings["heart_beat"]
|
38
|
+
@connection_uri = settings["url"]
|
39
|
+
@exchange_name = settings["exchange_name"]
|
40
|
+
@exchange_type = settings["exchange_type"] || "topic"
|
41
|
+
@durable = settings["durable"]
|
42
|
+
@prefetch_number = settings["prefetch_number"]
|
43
|
+
@backend_application_id = settings["backend_application_id"]
|
44
|
+
end
|
45
|
+
|
46
|
+
# sets backend_app_id, but just once,
|
47
|
+
# otherwise raise am InvalidSettings error
|
48
|
+
def backend_application_id=(backend_application_id)
|
49
|
+
raise InvalidSettingsError, "Backend Application ID has been set already." if @backend_application_id
|
50
|
+
@backend_application_id = backend_application_id
|
51
|
+
end
|
52
|
+
|
53
|
+
# Executed after a connection loss
|
54
|
+
# The exception should be catched and rollback the actions.
|
55
|
+
def connection_failure_handler
|
56
|
+
Proc.new do
|
57
|
+
raise ConnectionError, "can't connect to RabbitMQ server"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Create a new connection to the broker using
|
62
|
+
# the connection settings.
|
63
|
+
# Create a channel and setup a new exchange.
|
64
|
+
def connect
|
65
|
+
begin
|
66
|
+
if valid?
|
67
|
+
options = @heart_beat ? { :heartbeat => heart_beat } : {}
|
68
|
+
@connection = Bunny.new(connection_uri, options)
|
69
|
+
@connection.start
|
70
|
+
@channel = @connection.create_channel
|
71
|
+
set_prefetch_number(prefetch_number)
|
72
|
+
set_exchange(exchange_name, :durable => durable)
|
73
|
+
else
|
74
|
+
raise InvalidSettingsError, "settings are invalid"
|
75
|
+
end
|
76
|
+
rescue Bunny::TCPConnectionFailed, Bunny::PossibleAuthenticationFailureError => e
|
77
|
+
connection_failure_handler.call
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Close the connection
|
82
|
+
def close
|
83
|
+
@connection.close
|
84
|
+
end
|
85
|
+
|
86
|
+
# Create (or get if it already exists) a new topic
|
87
|
+
# exchange with the given options.
|
88
|
+
# Especially, the durable option can be set here to
|
89
|
+
# mark the exchange as durable (survive a server restart)
|
90
|
+
# @param [String] name
|
91
|
+
# @param [Hash] exchange options
|
92
|
+
def set_exchange(exchange_name, options = {})
|
93
|
+
@exchange = Bunny::Exchange.new(@channel, exchange_type.to_sym, exchange_name , options)
|
94
|
+
end
|
95
|
+
private :set_exchange
|
96
|
+
|
97
|
+
# Specifies the number of messages to prefetch.
|
98
|
+
# @param [int] number of messages to prefetch
|
99
|
+
def set_prefetch_number(number)
|
100
|
+
@channel.prefetch(number)
|
101
|
+
end
|
102
|
+
private :set_prefetch_number
|
103
|
+
|
104
|
+
# Set the message persistence behaviour.
|
105
|
+
# If persistent, the message will be persisted to disk
|
106
|
+
# and remain in the queue until it is consumed.
|
107
|
+
# Survive a server restart.
|
108
|
+
# BUNNY ISSUE: bunny0.9pre4 hardcodes the persistent option.
|
109
|
+
# @see lib/bunny/channel.rb:174 :delivery_mode => 2
|
110
|
+
# It is set all the time, meaning the messages will survive
|
111
|
+
# a server restart, if the queue and the exchange are durable.
|
112
|
+
# @param [Bool] persistence
|
113
|
+
def set_message_persistence(persistent)
|
114
|
+
@message_persistence = persistent
|
115
|
+
end
|
116
|
+
|
117
|
+
# Publish a message on the bus with the given options
|
118
|
+
# The routing key is passed in the options.
|
119
|
+
# @param [String] JSON message
|
120
|
+
# @param [Hash] publishing options
|
121
|
+
def publish(message, options = {})
|
122
|
+
raise ConnectionError, "exchange is not reachable" unless @exchange.instance_of?(Bunny::Exchange)
|
123
|
+
|
124
|
+
options.merge!(:persistent => @message_persistence) unless @message_persistence.nil?
|
125
|
+
options.merge!(:app_id => @backend_application_id) if @backend_application_id
|
126
|
+
@exchange.publish(message, options)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|