zookeeper-ng 1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.ctags_paths +1 -0
- data/.dotfiles/ruby-gemset +1 -0
- data/.dotfiles/ruby-version +1 -0
- data/.dotfiles/rvmrc +2 -0
- data/.gitignore +19 -0
- data/.gitmodules +3 -0
- data/.travis.yml +25 -0
- data/CHANGELOG +395 -0
- data/Gemfile +30 -0
- data/Guardfile +8 -0
- data/LICENSE +23 -0
- data/Manifest +29 -0
- data/README.markdown +85 -0
- data/Rakefile +121 -0
- data/cause-abort.rb +117 -0
- data/ext/.gitignore +6 -0
- data/ext/Rakefile +41 -0
- data/ext/c_zookeeper.rb +398 -0
- data/ext/common.h +17 -0
- data/ext/dbg.h +53 -0
- data/ext/depend +5 -0
- data/ext/event_lib.c +740 -0
- data/ext/event_lib.h +175 -0
- data/ext/extconf.rb +103 -0
- data/ext/generate_gvl_code.rb +321 -0
- data/ext/patches/zkc-3.3.5-network.patch +24 -0
- data/ext/patches/zkc-3.4.5-fetch-and-add.patch +16 -0
- data/ext/patches/zkc-3.4.5-logging.patch +41 -0
- data/ext/patches/zkc-3.4.5-out-of-order-ping.patch +163 -0
- data/ext/patches/zkc-3.4.5-overflow.patch +11 -0
- data/ext/patches/zkc-3.4.5-yosemite-htonl-fix.patch +102 -0
- data/ext/zkc-3.4.5.tar.gz +0 -0
- data/ext/zkrb.c +1075 -0
- data/ext/zkrb_wrapper.c +775 -0
- data/ext/zkrb_wrapper.h +350 -0
- data/ext/zkrb_wrapper_compat.c +15 -0
- data/ext/zkrb_wrapper_compat.h +11 -0
- data/ext/zookeeper_base.rb +256 -0
- data/java/java_base.rb +503 -0
- data/lib/zookeeper.rb +115 -0
- data/lib/zookeeper/acls.rb +44 -0
- data/lib/zookeeper/callbacks.rb +108 -0
- data/lib/zookeeper/client.rb +30 -0
- data/lib/zookeeper/client_methods.rb +282 -0
- data/lib/zookeeper/common.rb +122 -0
- data/lib/zookeeper/common/queue_with_pipe.rb +110 -0
- data/lib/zookeeper/compatibility.rb +138 -0
- data/lib/zookeeper/constants.rb +97 -0
- data/lib/zookeeper/continuation.rb +223 -0
- data/lib/zookeeper/core_ext.rb +58 -0
- data/lib/zookeeper/em_client.rb +55 -0
- data/lib/zookeeper/exceptions.rb +135 -0
- data/lib/zookeeper/forked.rb +19 -0
- data/lib/zookeeper/latch.rb +34 -0
- data/lib/zookeeper/logger.rb +39 -0
- data/lib/zookeeper/logger/forwarding_logger.rb +84 -0
- data/lib/zookeeper/monitor.rb +19 -0
- data/lib/zookeeper/rake_tasks.rb +165 -0
- data/lib/zookeeper/request_registry.rb +153 -0
- data/lib/zookeeper/stat.rb +21 -0
- data/lib/zookeeper/version.rb +4 -0
- data/notes.txt +14 -0
- data/scripts/upgrade-1.0-sed-alike.rb +46 -0
- data/spec/c_zookeeper_spec.rb +51 -0
- data/spec/chrooted_connection_spec.rb +83 -0
- data/spec/compatibilty_spec.rb +8 -0
- data/spec/default_watcher_spec.rb +41 -0
- data/spec/em_spec.rb +51 -0
- data/spec/ext/zookeeper_base_spec.rb +19 -0
- data/spec/forked_connection_spec.rb +124 -0
- data/spec/latch_spec.rb +24 -0
- data/spec/log4j.properties +17 -0
- data/spec/shared/all_success_return_values.rb +10 -0
- data/spec/shared/connection_examples.rb +1077 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/support/00_logging.rb +38 -0
- data/spec/support/10_spawn_zookeeper.rb +24 -0
- data/spec/support/progress_formatter.rb +15 -0
- data/spec/support/zookeeper_spec_helpers.rb +96 -0
- data/spec/zookeeper_spec.rb +24 -0
- data/zookeeper.gemspec +38 -0
- data/zoomonkey/duplicates +3 -0
- data/zoomonkey/zoomonkey.rb +194 -0
- metadata +157 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'zookeeper/exceptions'
|
2
|
+
require 'zookeeper/common/queue_with_pipe'
|
3
|
+
|
4
|
+
module Zookeeper
|
5
|
+
module Common
|
6
|
+
def event_dispatch_thread?
|
7
|
+
@dispatcher && (@dispatcher == Thread.current)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def setup_dispatch_thread!
|
12
|
+
@mutex.synchronize do
|
13
|
+
if @dispatcher
|
14
|
+
logger.debug { "dispatcher already running" }
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
logger.debug { "starting dispatch thread" }
|
19
|
+
|
20
|
+
@dispatcher = Thread.new(&method(:dispatch_thread_body))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# this method is part of the reopen/close code, and is responsible for
|
25
|
+
# shutting down the dispatch thread.
|
26
|
+
#
|
27
|
+
# @dispatcher will be nil when this method exits
|
28
|
+
#
|
29
|
+
def stop_dispatch_thread!(timeout=2)
|
30
|
+
logger.debug { "#{self.class}##{__method__}" }
|
31
|
+
|
32
|
+
if @dispatcher
|
33
|
+
if @dispatcher.join(0)
|
34
|
+
@dispatcher = nil
|
35
|
+
return
|
36
|
+
end
|
37
|
+
|
38
|
+
@mutex.synchronize do
|
39
|
+
event_queue.graceful_close!
|
40
|
+
|
41
|
+
# we now release the mutex so that dispatch_next_callback can grab it
|
42
|
+
# to do what it needs to do while delivering events
|
43
|
+
#
|
44
|
+
@dispatch_shutdown_cond.wait
|
45
|
+
|
46
|
+
# wait for another timeout sec for the thread to join
|
47
|
+
until @dispatcher.join(timeout)
|
48
|
+
logger.error { "Dispatch thread did not join cleanly, waiting" }
|
49
|
+
end
|
50
|
+
@dispatcher = nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_next_event(blocking=true)
|
56
|
+
@event_queue.pop(!blocking).tap do |event|
|
57
|
+
logger.debug { "#{self.class}##{__method__} delivering event #{event.inspect}" }
|
58
|
+
end
|
59
|
+
rescue ThreadError
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def dispatch_next_callback(hash)
|
64
|
+
return nil unless hash
|
65
|
+
|
66
|
+
logger.debug { "get_next_event returned: #{prettify_event(hash).inspect}" }
|
67
|
+
|
68
|
+
is_completion = hash.has_key?(:rc)
|
69
|
+
|
70
|
+
hash[:stat] = Zookeeper::Stat.new(hash[:stat]) if hash.has_key?(:stat)
|
71
|
+
hash[:acl] = hash[:acl].map { |acl| Zookeeper::ACLs::ACL.new(acl) } if hash[:acl]
|
72
|
+
|
73
|
+
callback_context = @req_registry.get_context_for(hash)
|
74
|
+
|
75
|
+
if callback_context
|
76
|
+
callback = is_completion ? callback_context[:callback] : callback_context[:watcher]
|
77
|
+
|
78
|
+
hash[:context] = callback_context[:context]
|
79
|
+
|
80
|
+
if callback.respond_to?(:call)
|
81
|
+
callback.call(hash)
|
82
|
+
else
|
83
|
+
# puts "dispatch_next_callback found non-callback => #{callback.inspect}"
|
84
|
+
end
|
85
|
+
else
|
86
|
+
logger.warn { "Duplicate event received (no handler for req_id #{hash[:req_id]}, event: #{hash.inspect}" }
|
87
|
+
end
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
def dispatch_thread_body
|
92
|
+
while true
|
93
|
+
begin
|
94
|
+
dispatch_next_callback(get_next_event(true))
|
95
|
+
rescue QueueWithPipe::ShutdownException
|
96
|
+
logger.info { "dispatch thread exiting, got shutdown exception" }
|
97
|
+
return
|
98
|
+
rescue Exception => e
|
99
|
+
$stderr.puts ["#{e.class}: #{e.message}", e.backtrace.map { |n| "\t#{n}" }.join("\n")].join("\n")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
ensure
|
103
|
+
signal_dispatch_thread_exit!
|
104
|
+
end
|
105
|
+
|
106
|
+
def signal_dispatch_thread_exit!
|
107
|
+
@mutex.synchronize do
|
108
|
+
logger.debug { "dispatch thread exiting!" }
|
109
|
+
@dispatch_shutdown_cond.broadcast
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def prettify_event(hash)
|
114
|
+
hash.dup.tap do |h|
|
115
|
+
# pretty up the event display
|
116
|
+
h[:type] = Zookeeper::Constants::EVENT_TYPE_NAMES.fetch(h[:type]) if h[:type]
|
117
|
+
h[:state] = Zookeeper::Constants::STATE_NAMES.fetch(h[:state]) if h[:state]
|
118
|
+
h[:req_id] = :global_session if h[:req_id] == -1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Zookeeper
|
2
|
+
module Common
|
3
|
+
# Ceci n'est pas une pipe
|
4
|
+
class QueueWithPipe
|
5
|
+
extend Forwardable
|
6
|
+
include Logger
|
7
|
+
|
8
|
+
# raised when close has been called, and pop() is performed
|
9
|
+
#
|
10
|
+
class ShutdownException < StandardError; end
|
11
|
+
|
12
|
+
# @private
|
13
|
+
KILL_TOKEN = Object.new unless defined?(KILL_TOKEN)
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@array = []
|
17
|
+
|
18
|
+
@mutex = Mutex.new
|
19
|
+
@cond = ConditionVariable.new
|
20
|
+
@closed = false
|
21
|
+
@graceful = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear
|
25
|
+
@mutex.lock
|
26
|
+
begin
|
27
|
+
@array.clear
|
28
|
+
ensure
|
29
|
+
@mutex.unlock rescue nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def push(obj)
|
34
|
+
@mutex.lock
|
35
|
+
begin
|
36
|
+
# raise ShutdownException if (@closed or @graceful)
|
37
|
+
@array << obj
|
38
|
+
@cond.signal
|
39
|
+
ensure
|
40
|
+
@mutex.unlock rescue nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def pop(non_blocking=false)
|
45
|
+
rval = nil
|
46
|
+
|
47
|
+
@mutex.lock
|
48
|
+
begin
|
49
|
+
|
50
|
+
begin
|
51
|
+
raise ShutdownException if @closed # this may get us in trouble
|
52
|
+
|
53
|
+
rval = @array.shift
|
54
|
+
|
55
|
+
unless rval
|
56
|
+
raise ThreadError if non_blocking # sigh, ruby's stupid behavior
|
57
|
+
raise ShutdownException if @graceful # we've processed all the remaining mesages
|
58
|
+
|
59
|
+
@cond.wait(@mutex) until (@closed or @graceful or (@array.length > 0))
|
60
|
+
end
|
61
|
+
end until rval
|
62
|
+
|
63
|
+
return rval
|
64
|
+
|
65
|
+
ensure
|
66
|
+
@mutex.unlock rescue nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# close the queue and causes ShutdownException to be raised on waiting threads
|
71
|
+
def graceful_close!
|
72
|
+
@mutex.lock
|
73
|
+
begin
|
74
|
+
return if @graceful or @closed
|
75
|
+
logger.debug { "#{self.class}##{__method__} gracefully closing" }
|
76
|
+
@graceful = true
|
77
|
+
@cond.broadcast
|
78
|
+
ensure
|
79
|
+
@mutex.unlock rescue nil
|
80
|
+
end
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
|
84
|
+
def open
|
85
|
+
@mutex.lock
|
86
|
+
begin
|
87
|
+
@closed = @graceful = false
|
88
|
+
@cond.broadcast
|
89
|
+
ensure
|
90
|
+
@mutex.unlock rescue nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def close
|
95
|
+
@mutex.lock
|
96
|
+
begin
|
97
|
+
return if @closed
|
98
|
+
@closed = true
|
99
|
+
@cond.broadcast
|
100
|
+
ensure
|
101
|
+
@mutex.unlock rescue nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def closed?
|
106
|
+
@mutex.synchronize { !!@closed }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Zookeeper
|
2
|
+
# @private
|
3
|
+
def self.warn_about_compatability_once!
|
4
|
+
return if @warned_about_compatibility
|
5
|
+
@warned_about_compatibility = true
|
6
|
+
|
7
|
+
warn <<-EOS
|
8
|
+
|
9
|
+
-----------------------------------------------------------------------------
|
10
|
+
|
11
|
+
NOTICE: ZOOKEEPER BACKWARDS COMPATIBILTY EANBLED!!
|
12
|
+
|
13
|
+
THIS WILL NOT BE AUTOMATIC IN 1.1 !!
|
14
|
+
|
15
|
+
There was a major change to the organization of the Zookeeper gem between
|
16
|
+
0.9 and 1.0, breaking backwards compatibility. To ease the transition,
|
17
|
+
|
18
|
+
#{__FILE__}
|
19
|
+
|
20
|
+
is automatically required. This will *not* be the case in 1.1.
|
21
|
+
|
22
|
+
-----------------------------------------------------------------------------
|
23
|
+
EOS
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.warned_about_compatability?
|
27
|
+
!!@warned_about_compatability
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# at request of @eric
|
32
|
+
#Zookeeper.warn_about_compatability_once!
|
33
|
+
|
34
|
+
module Zookeeper
|
35
|
+
module Compatibility
|
36
|
+
def clean_backtrace
|
37
|
+
caller[0..-2].reject {|n| n =~ %r%/rspec/|\(eval\)|const_missing% }.map { |n| "\t#{n}" }.join("\n")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module ZookeeperConstants
|
43
|
+
include Zookeeper::Constants
|
44
|
+
end
|
45
|
+
|
46
|
+
module ZookeeperCallbacks
|
47
|
+
include Zookeeper::Callbacks
|
48
|
+
Callback = Base
|
49
|
+
end
|
50
|
+
|
51
|
+
module ZookeeperExceptions
|
52
|
+
include Zookeeper::Exceptions
|
53
|
+
end
|
54
|
+
|
55
|
+
module ZookeeperStat
|
56
|
+
extend Zookeeper::Compatibility
|
57
|
+
def self.const_missing(sym)
|
58
|
+
if sym == :Stat
|
59
|
+
warn "\nZookeeperStat::Stat is now Zookeeper::Stat, please update your code!\n#{clean_backtrace}"
|
60
|
+
# self.const_set(sym, Zookeeper::Stat)
|
61
|
+
Zookeeper::Stat
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module ZookeeperACLs
|
69
|
+
extend Zookeeper::Compatibility
|
70
|
+
def self.const_missing(sym)
|
71
|
+
candidates = [Zookeeper::ACLs, Zookeeper::Constants, Zookeeper::ACLs::Constants]
|
72
|
+
|
73
|
+
candidates.each do |candidate|
|
74
|
+
if candidate.const_defined?(sym)
|
75
|
+
warn "\n#{self.name}::#{sym} is now located in #{candidate}::#{sym}, please update your code!\n#{clean_backtrace}"
|
76
|
+
|
77
|
+
c = candidate.const_get(sym)
|
78
|
+
# self.const_set(sym, c)
|
79
|
+
return c
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
super
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
module ZookeeperCommon
|
88
|
+
include Zookeeper::Common
|
89
|
+
extend Zookeeper::Compatibility
|
90
|
+
|
91
|
+
def self.const_missing(sym)
|
92
|
+
candidate = Zookeeper::Common
|
93
|
+
|
94
|
+
if candidate.const_defined?(sym)
|
95
|
+
warn "\n#{self.name}::#{sym} is now located in #{candidate}::#{sym}, please update your code!\n#{clean_backtrace}"
|
96
|
+
|
97
|
+
candidate.const_get(sym).tap do |c|
|
98
|
+
# self.const_set(sym, c)
|
99
|
+
end
|
100
|
+
else
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
# module Zookeeper
|
108
|
+
# include ZookeeperConstants
|
109
|
+
# include ZookeeperCallbacks
|
110
|
+
# include ZookeeperExceptions
|
111
|
+
# include ZookeeperCommon
|
112
|
+
# include ZookeeperStat
|
113
|
+
# include ZookeeperACLs
|
114
|
+
# end
|
115
|
+
|
116
|
+
module Zookeeper
|
117
|
+
extend Zookeeper::Compatibility
|
118
|
+
def self.const_missing(sym)
|
119
|
+
candidate =
|
120
|
+
case sym.to_s
|
121
|
+
when /Callback/
|
122
|
+
Zookeeper::Callbacks
|
123
|
+
end
|
124
|
+
|
125
|
+
super unless candidate
|
126
|
+
|
127
|
+
if candidate.const_defined?(sym)
|
128
|
+
warn "\n#{self.name}::#{sym} is now located in #{candidate}::#{sym}, please update your code!\n#{clean_backtrace}"
|
129
|
+
|
130
|
+
candidate.const_get(sym).tap do |c|
|
131
|
+
# self.const_set(sym, c)
|
132
|
+
end
|
133
|
+
else
|
134
|
+
super
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Zookeeper
|
2
|
+
module Constants
|
3
|
+
include ACLs::Constants
|
4
|
+
|
5
|
+
# file type masks
|
6
|
+
ZOO_EPHEMERAL = 1
|
7
|
+
ZOO_SEQUENCE = 2
|
8
|
+
|
9
|
+
# session state
|
10
|
+
ZOO_EXPIRED_SESSION_STATE = -112
|
11
|
+
ZOO_AUTH_FAILED_STATE = -113
|
12
|
+
ZOO_CLOSED_STATE = 0
|
13
|
+
ZOO_CONNECTING_STATE = 1
|
14
|
+
ZOO_ASSOCIATING_STATE = 2
|
15
|
+
ZOO_CONNECTED_STATE = 3
|
16
|
+
|
17
|
+
# watch types
|
18
|
+
ZOO_CREATED_EVENT = 1
|
19
|
+
ZOO_DELETED_EVENT = 2
|
20
|
+
ZOO_CHANGED_EVENT = 3
|
21
|
+
ZOO_CHILD_EVENT = 4
|
22
|
+
ZOO_SESSION_EVENT = -1
|
23
|
+
ZOO_NOTWATCHING_EVENT = -2
|
24
|
+
|
25
|
+
# only used by the C extension
|
26
|
+
ZOO_LOG_LEVEL_ERROR = 1
|
27
|
+
ZOO_LOG_LEVEL_WARN = 2
|
28
|
+
ZOO_LOG_LEVEL_INFO = 3
|
29
|
+
ZOO_LOG_LEVEL_DEBUG = 4
|
30
|
+
|
31
|
+
# exceptions/errors
|
32
|
+
ZOK = 0
|
33
|
+
ZSYSTEMERROR = -1
|
34
|
+
ZRUNTIMEINCONSISTENCY = -2
|
35
|
+
ZDATAINCONSISTENCY = -3
|
36
|
+
ZCONNECTIONLOSS = -4
|
37
|
+
ZMARSHALLINGERROR = -5
|
38
|
+
ZUNIMPLEMENTED = -6
|
39
|
+
ZOPERATIONTIMEOUT = -7
|
40
|
+
ZBADARGUMENTS = -8
|
41
|
+
ZINVALIDSTATE = -9
|
42
|
+
|
43
|
+
# api errors
|
44
|
+
ZAPIERROR = -100
|
45
|
+
ZNONODE = -101
|
46
|
+
ZNOAUTH = -102
|
47
|
+
ZBADVERSION = -103
|
48
|
+
ZNOCHILDRENFOREPHEMERALS = -108
|
49
|
+
ZNODEEXISTS = -110
|
50
|
+
ZNOTEMPTY = -111
|
51
|
+
ZSESSIONEXPIRED = -112
|
52
|
+
ZINVALIDCALLBACK = -113
|
53
|
+
ZINVALIDACL = -114
|
54
|
+
ZAUTHFAILED = -115
|
55
|
+
ZCLOSING = -116
|
56
|
+
ZNOTHING = -117
|
57
|
+
ZSESSIONMOVED = -118
|
58
|
+
|
59
|
+
ZKRB_GLOBAL_CB_REQ = -1
|
60
|
+
ZKRB_ASYNC_CONTN_ID = -2
|
61
|
+
|
62
|
+
# @private
|
63
|
+
CONNECTED_EVENT_VALUES = [Constants::ZKRB_GLOBAL_CB_REQ,
|
64
|
+
Constants::ZOO_SESSION_EVENT,
|
65
|
+
Constants::ZOO_CONNECTED_STATE].freeze
|
66
|
+
|
67
|
+
# used to find the name for a numeric event
|
68
|
+
# @private
|
69
|
+
EVENT_TYPE_NAMES = {
|
70
|
+
1 => 'created',
|
71
|
+
2 => 'deleted',
|
72
|
+
3 => 'changed',
|
73
|
+
4 => 'child',
|
74
|
+
-1 => 'session',
|
75
|
+
-2 => 'notwatching',
|
76
|
+
}
|
77
|
+
|
78
|
+
# used to pretty print the state name
|
79
|
+
# @private
|
80
|
+
STATE_NAMES = {
|
81
|
+
-112 => 'expired_session',
|
82
|
+
-113 => 'auth_failed',
|
83
|
+
0 => 'closed',
|
84
|
+
1 => 'connecting',
|
85
|
+
2 => 'associating',
|
86
|
+
3 => 'connected',
|
87
|
+
}
|
88
|
+
|
89
|
+
def event_by_value(v)
|
90
|
+
(name = EVENT_TYPE_NAMES[v]) ? "ZOO_#{name.upcase}_EVENT" : ''
|
91
|
+
end
|
92
|
+
|
93
|
+
def state_by_value(v)
|
94
|
+
(name = STATE_NAMES[v]) ? "ZOO_#{name.upcase}_STATE" : ''
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|