arrow 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (198) hide show
  1. data/ChangeLog +1590 -0
  2. data/LICENSE +28 -0
  3. data/README +75 -0
  4. data/Rakefile +366 -0
  5. data/Rakefile.local +63 -0
  6. data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
  7. data/data/arrow/applets/args.rb +50 -0
  8. data/data/arrow/applets/config.rb +55 -0
  9. data/data/arrow/applets/error.rb +63 -0
  10. data/data/arrow/applets/files.rb +46 -0
  11. data/data/arrow/applets/inspect.rb +46 -0
  12. data/data/arrow/applets/nosuchapplet.rb +31 -0
  13. data/data/arrow/applets/status.rb +92 -0
  14. data/data/arrow/applets/test.rb +133 -0
  15. data/data/arrow/applets/tutorial/counter.rb +96 -0
  16. data/data/arrow/applets/tutorial/dingus.rb +67 -0
  17. data/data/arrow/applets/tutorial/hello.rb +34 -0
  18. data/data/arrow/applets/tutorial/hello2.rb +73 -0
  19. data/data/arrow/applets/tutorial/imgtext.rb +90 -0
  20. data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
  21. data/data/arrow/applets/tutorial/index.rb +36 -0
  22. data/data/arrow/applets/tutorial/logo.rb +98 -0
  23. data/data/arrow/applets/tutorial/memcache.rb +61 -0
  24. data/data/arrow/applets/tutorial/missing.rb +37 -0
  25. data/data/arrow/applets/tutorial/protected.rb +100 -0
  26. data/data/arrow/applets/tutorial/redirector.rb +52 -0
  27. data/data/arrow/applets/tutorial/rndimages.rb +159 -0
  28. data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
  29. data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
  30. data/data/arrow/applets/tutorial/superhello.rb +72 -0
  31. data/data/arrow/applets/tutorial/timeclock.rb +78 -0
  32. data/data/arrow/applets/view-applet.rb +123 -0
  33. data/data/arrow/applets/view-template.rb +85 -0
  34. data/data/arrow/applets/wiki.rb +274 -0
  35. data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
  36. data/data/arrow/templates/applet-status.tmpl +153 -0
  37. data/data/arrow/templates/args-display.tmpl +120 -0
  38. data/data/arrow/templates/config/display-table.tmpl +36 -0
  39. data/data/arrow/templates/config/display.tmpl +36 -0
  40. data/data/arrow/templates/counter-deleted.tmpl +33 -0
  41. data/data/arrow/templates/counter.tmpl +59 -0
  42. data/data/arrow/templates/dingus.tmpl +55 -0
  43. data/data/arrow/templates/enumtable.tmpl +8 -0
  44. data/data/arrow/templates/error-display.tmpl +92 -0
  45. data/data/arrow/templates/filemap.tmpl +89 -0
  46. data/data/arrow/templates/hello-world-src.tmpl +34 -0
  47. data/data/arrow/templates/hello-world.tmpl +60 -0
  48. data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
  49. data/data/arrow/templates/imgtext/form.tmpl +70 -0
  50. data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
  51. data/data/arrow/templates/imgtext/reload.tmpl +55 -0
  52. data/data/arrow/templates/inspect/display.tmpl +80 -0
  53. data/data/arrow/templates/loginform.tmpl +64 -0
  54. data/data/arrow/templates/logout.tmpl +32 -0
  55. data/data/arrow/templates/memcache/display.tmpl +41 -0
  56. data/data/arrow/templates/navbar.incl +27 -0
  57. data/data/arrow/templates/nosuchapplet.tmpl +32 -0
  58. data/data/arrow/templates/printsource.tmpl +35 -0
  59. data/data/arrow/templates/protected.tmpl +36 -0
  60. data/data/arrow/templates/rndimages.tmpl +38 -0
  61. data/data/arrow/templates/service-response.tmpl +13 -0
  62. data/data/arrow/templates/sharenotes/display.tmpl +38 -0
  63. data/data/arrow/templates/status.tmpl +120 -0
  64. data/data/arrow/templates/templateviewer.tmpl +43 -0
  65. data/data/arrow/templates/test/harness.tmpl +57 -0
  66. data/data/arrow/templates/test/list.tmpl +48 -0
  67. data/data/arrow/templates/test/problem.tmpl +42 -0
  68. data/data/arrow/templates/tutorial/index.tmpl +37 -0
  69. data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
  70. data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
  71. data/data/arrow/templates/view-applet.tmpl +40 -0
  72. data/data/arrow/templates/view-template.tmpl +83 -0
  73. data/data/arrow/templates/wiki/formerror.tmpl +47 -0
  74. data/data/arrow/templates/wiki/markup_help.incl +6 -0
  75. data/data/arrow/templates/wiki/new.tmpl +56 -0
  76. data/data/arrow/templates/wiki/new_system.tmpl +122 -0
  77. data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
  78. data/data/arrow/templates/wiki/show.tmpl +34 -0
  79. data/docs/manual/layouts/default.page +43 -0
  80. data/docs/manual/lib/api-filter.rb +81 -0
  81. data/docs/manual/lib/editorial-filter.rb +64 -0
  82. data/docs/manual/lib/examples-filter.rb +244 -0
  83. data/docs/manual/lib/links-filter.rb +117 -0
  84. data/lib/apache/fakerequest.rb +448 -0
  85. data/lib/apache/logger.rb +33 -0
  86. data/lib/arrow.rb +51 -0
  87. data/lib/arrow/acceptparam.rb +207 -0
  88. data/lib/arrow/applet.rb +725 -0
  89. data/lib/arrow/appletmixins.rb +218 -0
  90. data/lib/arrow/appletregistry.rb +590 -0
  91. data/lib/arrow/applettestcase.rb +503 -0
  92. data/lib/arrow/broker.rb +255 -0
  93. data/lib/arrow/cache.rb +176 -0
  94. data/lib/arrow/config-loaders/yaml.rb +75 -0
  95. data/lib/arrow/config.rb +615 -0
  96. data/lib/arrow/constants.rb +24 -0
  97. data/lib/arrow/cookie.rb +359 -0
  98. data/lib/arrow/cookieset.rb +108 -0
  99. data/lib/arrow/dispatcher.rb +368 -0
  100. data/lib/arrow/dispatcherloader.rb +50 -0
  101. data/lib/arrow/exceptions.rb +61 -0
  102. data/lib/arrow/fallbackhandler.rb +48 -0
  103. data/lib/arrow/formvalidator.rb +631 -0
  104. data/lib/arrow/htmltokenizer.rb +343 -0
  105. data/lib/arrow/logger.rb +488 -0
  106. data/lib/arrow/logger/apacheoutputter.rb +69 -0
  107. data/lib/arrow/logger/arrayoutputter.rb +63 -0
  108. data/lib/arrow/logger/coloroutputter.rb +111 -0
  109. data/lib/arrow/logger/fileoutputter.rb +96 -0
  110. data/lib/arrow/logger/htmloutputter.rb +54 -0
  111. data/lib/arrow/logger/outputter.rb +123 -0
  112. data/lib/arrow/mixins.rb +425 -0
  113. data/lib/arrow/monkeypatches.rb +94 -0
  114. data/lib/arrow/object.rb +117 -0
  115. data/lib/arrow/path.rb +196 -0
  116. data/lib/arrow/service.rb +447 -0
  117. data/lib/arrow/session.rb +289 -0
  118. data/lib/arrow/session/dbstore.rb +100 -0
  119. data/lib/arrow/session/filelock.rb +160 -0
  120. data/lib/arrow/session/filestore.rb +132 -0
  121. data/lib/arrow/session/id.rb +98 -0
  122. data/lib/arrow/session/lock.rb +253 -0
  123. data/lib/arrow/session/md5id.rb +42 -0
  124. data/lib/arrow/session/nulllock.rb +42 -0
  125. data/lib/arrow/session/posixlock.rb +166 -0
  126. data/lib/arrow/session/sha1id.rb +54 -0
  127. data/lib/arrow/session/store.rb +366 -0
  128. data/lib/arrow/session/usertrackid.rb +52 -0
  129. data/lib/arrow/spechelpers.rb +73 -0
  130. data/lib/arrow/template.rb +713 -0
  131. data/lib/arrow/template/attr.rb +31 -0
  132. data/lib/arrow/template/call.rb +31 -0
  133. data/lib/arrow/template/comment.rb +33 -0
  134. data/lib/arrow/template/container.rb +118 -0
  135. data/lib/arrow/template/else.rb +41 -0
  136. data/lib/arrow/template/elsif.rb +44 -0
  137. data/lib/arrow/template/escape.rb +53 -0
  138. data/lib/arrow/template/export.rb +87 -0
  139. data/lib/arrow/template/for.rb +145 -0
  140. data/lib/arrow/template/if.rb +78 -0
  141. data/lib/arrow/template/import.rb +119 -0
  142. data/lib/arrow/template/include.rb +206 -0
  143. data/lib/arrow/template/iterator.rb +208 -0
  144. data/lib/arrow/template/nodes.rb +734 -0
  145. data/lib/arrow/template/parser.rb +571 -0
  146. data/lib/arrow/template/prettyprint.rb +53 -0
  147. data/lib/arrow/template/render.rb +191 -0
  148. data/lib/arrow/template/selectlist.rb +94 -0
  149. data/lib/arrow/template/set.rb +87 -0
  150. data/lib/arrow/template/timedelta.rb +81 -0
  151. data/lib/arrow/template/unless.rb +78 -0
  152. data/lib/arrow/template/urlencode.rb +51 -0
  153. data/lib/arrow/template/yield.rb +139 -0
  154. data/lib/arrow/templatefactory.rb +125 -0
  155. data/lib/arrow/testcase.rb +567 -0
  156. data/lib/arrow/transaction.rb +608 -0
  157. data/rake/191_compat.rb +26 -0
  158. data/rake/dependencies.rb +76 -0
  159. data/rake/documentation.rb +114 -0
  160. data/rake/helpers.rb +502 -0
  161. data/rake/hg.rb +282 -0
  162. data/rake/manual.rb +787 -0
  163. data/rake/packaging.rb +129 -0
  164. data/rake/publishing.rb +278 -0
  165. data/rake/style.rb +62 -0
  166. data/rake/svn.rb +668 -0
  167. data/rake/testing.rb +187 -0
  168. data/rake/verifytask.rb +64 -0
  169. data/spec/arrow/acceptparam_spec.rb +157 -0
  170. data/spec/arrow/applet_spec.rb +575 -0
  171. data/spec/arrow/appletmixins_spec.rb +409 -0
  172. data/spec/arrow/appletregistry_spec.rb +294 -0
  173. data/spec/arrow/broker_spec.rb +153 -0
  174. data/spec/arrow/config_spec.rb +224 -0
  175. data/spec/arrow/cookieset_spec.rb +164 -0
  176. data/spec/arrow/dispatcher_spec.rb +137 -0
  177. data/spec/arrow/dispatcherloader_spec.rb +65 -0
  178. data/spec/arrow/formvalidator_spec.rb +781 -0
  179. data/spec/arrow/logger_spec.rb +346 -0
  180. data/spec/arrow/mixins_spec.rb +120 -0
  181. data/spec/arrow/service_spec.rb +645 -0
  182. data/spec/arrow/session_spec.rb +121 -0
  183. data/spec/arrow/template/iterator_spec.rb +222 -0
  184. data/spec/arrow/templatefactory_spec.rb +185 -0
  185. data/spec/arrow/transaction_spec.rb +319 -0
  186. data/spec/arrow_spec.rb +37 -0
  187. data/spec/lib/appletmatchers.rb +281 -0
  188. data/spec/lib/constants.rb +77 -0
  189. data/spec/lib/helpers.rb +41 -0
  190. data/spec/lib/matchers.rb +44 -0
  191. data/tests/cookie.tests.rb +310 -0
  192. data/tests/path.tests.rb +157 -0
  193. data/tests/session.tests.rb +111 -0
  194. data/tests/session_id.tests.rb +82 -0
  195. data/tests/session_lock.tests.rb +191 -0
  196. data/tests/session_store.tests.rb +53 -0
  197. data/tests/template.tests.rb +1360 -0
  198. metadata +339 -0
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'digest/md5'
4
+
5
+ require 'arrow/session/id'
6
+
7
+ # The Arrow::Session::MD5Id class, a derivative of Arrow::Session::Id. Instances
8
+ # of this class are session IDs created by MD5-hashing some semi-random data.
9
+ #
10
+ # == Authors
11
+ #
12
+ # * Michael Granger <ged@FaerieMUD.org>
13
+ #
14
+ # Please see the file LICENSE in the top-level directory for licensing details.
15
+ #
16
+ class Arrow::Session::MD5Id < Arrow::Session::Id
17
+
18
+
19
+ #############################################################
20
+ ### C L A S S M E T H O D S
21
+ #############################################################
22
+
23
+ ### Generate a new id
24
+ def self::generate( uri, request )
25
+ seed = [
26
+ Time.new.to_s,
27
+ Object.new.inspect,
28
+ rand(),
29
+ Process.pid,
30
+ ].join
31
+ return Digest::MD5.hexdigest( Digest::MD5.hexdigest(seed) )
32
+ end
33
+
34
+ ### Returns the validated id if the given id is in the expected form for
35
+ ### this type, or +nil+ if it is not.
36
+ def self::validate( uri, idstr )
37
+ rval = idstr[/^([a-f0-9]{32})$/]
38
+ rval.untaint
39
+ return rval
40
+ end
41
+
42
+ end # class Arrow::Session::MD5Id
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'arrow/session/lock'
4
+
5
+ # The Arrow::Session::NullLock class, a derivative of
6
+ # Arrow::Session::Lock. This is a null lock, in that it does not lock.
7
+ # This is to be used with an ActiveRecord session store that uses
8
+ # Optomistic Concurrency Control.
9
+ #
10
+ # == VCS Id
11
+ #
12
+ # $Id$
13
+ #
14
+ # == Authors
15
+ #
16
+ # * Jeremiah Jordan <phaedrus@FaerieMUD.org>
17
+ #
18
+ # Please see the file LICENSE in the top-level directory for licensing details.
19
+ #
20
+ class Arrow::Session::NullLock < Arrow::Session::Lock
21
+
22
+ def initialize( uri, id )
23
+ super
24
+ end
25
+
26
+ def acquire_read_lock(blocking)
27
+ true
28
+ end
29
+
30
+ def acquire_write_lock(blocking)
31
+ true
32
+ end
33
+
34
+ def release_read_lock
35
+ true
36
+ end
37
+
38
+ def release_write_lock
39
+ true
40
+ end
41
+
42
+ end
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'posixlock'
4
+ require 'ftools'
5
+
6
+ require 'arrow/session/lock'
7
+
8
+ # The Arrow::Session::PosixLock class, a derivative of
9
+ # Arrow::Session::Lock. This lock type uses the 'posixlock' library
10
+ # (http://raa.ruby-lang.org/project/posixlock/).
11
+ #
12
+ # == VCS Id
13
+ #
14
+ # $Id$
15
+ #
16
+ # == Authors
17
+ #
18
+ # * Michael Granger <ged@FaerieMUD.org>
19
+ #
20
+ # Please see the file LICENSE in the top-level directory for licensing details.
21
+ #
22
+ class Arrow::Session::PosixLock < Arrow::Session::Lock
23
+
24
+
25
+ # The path to the default lockdir
26
+ DefaultLockDir = '/tmp'
27
+
28
+ # The format string that will be used for the name of the lock file. The
29
+ # first '%s' will be replaced with a sanitized version of the session
30
+ # id.
31
+ LockfileFormat = "arrow-session-%s.plock"
32
+
33
+ # The mode to open the lockfile in
34
+ FileMode = File::CREAT|File::RDWR
35
+
36
+
37
+ #############################################################
38
+ ### C L A S S M E T H O D S
39
+ #############################################################
40
+
41
+ ### Clean the specified +directory+ of lock files older than +threshold+
42
+ ### seconds.
43
+ def self::clean( directory=DefaultLockDir, threshold=3600 )
44
+ pat = File.join( directory, LockfileFormat.gsub(/%s/, '*') )
45
+ threshold = Time.now - threshold
46
+ Dir[ pat ].each do |file|
47
+ if File.mtime( file ) < threshold
48
+ Arrow::Logger[self].info \
49
+ "Removing stale lockfile '%s'" % file
50
+ begin
51
+ fh = File.open( file, FileMode )
52
+ fh.posixlock( File::LOCK_EX|File::LOCK_NB )
53
+ File.delete( file )
54
+ fh.posixlock( File::LOCK_UN )
55
+ fh.close
56
+ rescue => err
57
+ Arrow::Logger[self].warning \
58
+ "Could not clean up '%s': %s" %
59
+ [ file, err.message ]
60
+ next
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+
68
+ #############################################################
69
+ ### I N S T A N C E M E T H O D S
70
+ #############################################################
71
+
72
+ ### Create a new Arrow::Session::FileLock object.
73
+ def initialize( uri, id )
74
+ @lockDir = uri.path || DefaultLockDir
75
+ super
76
+
77
+ File.mkpath( @lockDir )
78
+ @filename = File.join( @lockDir, LockfileFormat % id.to_s.gsub(/\W/, '_') ).untaint
79
+ self.log.debug "Filename is: #@filename"
80
+ @lockfile = nil
81
+ end
82
+
83
+
84
+ ######
85
+ public
86
+ ######
87
+
88
+ # The path to the directory where session lockfiles are kept.
89
+ attr_accessor :lockDir
90
+
91
+
92
+ ### Indicate to the lock that the caller will no longer be using it, and it
93
+ ### may free any resources it had been using.
94
+ def finish
95
+ super
96
+ self.close_lock_file
97
+ end
98
+
99
+
100
+
101
+ #########
102
+ protected
103
+ #########
104
+
105
+ ### Get the File object for the lockfile belonging to this lock,
106
+ ### creating it if necessary.
107
+ def lockfile
108
+ @lockfile ||= File.open( @filename, FileMode )
109
+ end
110
+
111
+
112
+ ### Close the lockfile and destroy the File object belonging to this
113
+ ### lock.
114
+ def close_lock_file
115
+ if @lockfile
116
+ path = @lockfile.path
117
+ @lockfile.close
118
+ @lockfile = nil
119
+ if File.exist?( path.untaint )
120
+ File.delete( path.untaint )
121
+ end
122
+ end
123
+ end
124
+
125
+
126
+ ### Acquire a read (shared) lock on the lockfile.
127
+ def acquire_read_lock( blocking )
128
+ flags = File::LOCK_SH
129
+ flags |= File::LOCK_NB if !blocking
130
+
131
+ self.lockfile.posixlock( flags )
132
+ end
133
+
134
+
135
+ ### Acquire a write (exclusive) lock on the lockfile.
136
+ def acquire_write_lock( blocking )
137
+ flags = File::LOCK_EX
138
+ flags |= File::LOCK_NB if !blocking
139
+
140
+ self.lockfile.posixlock( flags )
141
+ end
142
+
143
+
144
+ ### Release a previously-acquired read lock.
145
+ def release_read_lock
146
+ if !self.write_locked?
147
+ self.lockfile.posixlock( File::LOCK_UN )
148
+ self.close_lock_file
149
+ end
150
+ end
151
+
152
+
153
+ ### Release a previously-acquired write lock.
154
+ def release_write_lock
155
+ if self.read_locked?
156
+ self.lockfile.posixlock( File::LOCK_SH )
157
+ else
158
+ self.lockfile.posixlock( File::LOCK_UN )
159
+ self.close_lock_file
160
+ end
161
+ end
162
+
163
+ end # module Arrow::Session::PosixLock
164
+
165
+
166
+
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'digest/sha1'
4
+
5
+ require 'arrow/session/id'
6
+
7
+ # The Arrow::Session::SHA1Id class, a derivative of
8
+ # Arrow::Session::Id. Instances of this class are session IDs created by
9
+ # SHA1-hashing some semi-random data.
10
+ #
11
+ # == Synopsis
12
+ #
13
+ # # In arrow.conf:
14
+ # session:
15
+ # idType: sha1:.
16
+ #
17
+ # == Authors
18
+ #
19
+ # * Michael Granger <ged@FaerieMUD.org>
20
+ #
21
+ # Please see the file LICENSE in the top-level directory for licensing details.
22
+ #
23
+ class Arrow::Session::SHA1Id < Arrow::Session::Id
24
+
25
+ # Default salt characters
26
+ DEFAULT_SALT = 'sadblkw456jbhgsdfi7283hnehonaseegop26m'
27
+
28
+
29
+ #############################################################
30
+ ### C L A S S M E T H O D S
31
+ #############################################################
32
+
33
+ ### Generate a new id
34
+ def self::generate( uri, request )
35
+ salt = uri.opaque || DEFAULT_SALT
36
+ seed = [
37
+ salt,
38
+ Time.new.to_s,
39
+ Object.new.inspect,
40
+ rand(),
41
+ Process.pid,
42
+ ].join
43
+ return Digest::SHA1.hexdigest( Digest::SHA1.hexdigest(seed) )
44
+ end
45
+
46
+ ### Returns the validated id if the given id is in the expected form for
47
+ ### this type, or +nil+ if it is not.
48
+ def self::validate( uri, idstr )
49
+ rval = idstr[/^([a-f0-9]{40})$/]
50
+ rval.untaint
51
+ return rval
52
+ end
53
+
54
+ end # class Arrow::Session::MD5Id
@@ -0,0 +1,366 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'forwardable'
4
+ require 'pluginfactory'
5
+
6
+ require 'arrow/mixins'
7
+ require 'arrow/exceptions'
8
+ require 'arrow/session'
9
+ require 'arrow/session/lock'
10
+
11
+ # The Arrow::Session::Store class, a derivative of Arrow::Object. Instances
12
+ # of concrete deriviatives of this class provide serialization and semi-permanent
13
+ # storage of session data for Arrow::Session objects.
14
+ #
15
+ # === Derivative Interface ===
16
+ #
17
+ # In order to create your own session store classes, you need to provide four
18
+ # methods: #insert, #update, #retrieve, #remove. All but one of the methods
19
+ # provides serialization and marking records as dirty in the base class, so
20
+ # unless you want to manage these tasks yourself, you should +super()+ to the
21
+ # parent's implementation with a block. Examples are provided for each method.
22
+ #
23
+ # #insert::
24
+ # Insert a new session into the backing store. Example:
25
+ # def insert
26
+ # super {|data| @io.print(data) }
27
+ # end
28
+ #
29
+ # #update::
30
+ # Update an existing session's data in the backing store. Example:
31
+ # def update
32
+ # super {|data| @io.rewind; @io.truncate(0); @io.print(data) }
33
+ # end
34
+ #
35
+ # #retrieve::
36
+ # Retrieve the serialized session data from the backing store. Example:
37
+ # def retrieve
38
+ # super { @io.rewind; @io.read }
39
+ # end
40
+ #
41
+ # #delete::
42
+ # Delete the session from the backing store. Example:
43
+ # def delete
44
+ # super {|data| @io.close; File.delete(@session_file) }
45
+ # end
46
+ #
47
+ # === Optional Derivative Interface ===
48
+ # ==== Serialization ====
49
+ #
50
+ # If you want to use something other than Marshal for object serialization,
51
+ # you can override the protected methods #serialized_data and #serialized_data=
52
+ # to provide your own serialization.
53
+ #
54
+ # #serialized_data::
55
+ # Serialize the data in the instance variable +@data+ and return it.
56
+ # #serialized_data=( serialized )::
57
+ # Deserialize the given +serialized+ data and assign it to @data.
58
+ #
59
+ # Example (serializing to YAML instead of binary):
60
+ # require 'yaml'
61
+ #
62
+ # def serialized_data
63
+ # @data.to_yaml
64
+ # end
65
+ #
66
+ # def serialized_data=( data )
67
+ # @data = YAML.load( data )
68
+ # end
69
+ #
70
+ # ==== Lock Recommendation ====
71
+ #
72
+ # If arrow is configured to use the 'recommended' session lock, your session
73
+ # store can recommend one it knows will work (e.g., if your session store is
74
+ # a database, you can recommend a lock that uses database locking). The simple
75
+ # way to do that is to define a RecommendedLocker constant in your class which
76
+ # contains the URI of the locker you wish to use. If you need more control
77
+ # than the URI can provide, you can also override the #create_recommended_lock
78
+ # method, which should return an instance of the locker that should be used.
79
+ #
80
+ # The method will be given the instantiated Arrow::Session::Id object that
81
+ # identifies the session so that you can derive a filename, primary key, etc.
82
+ #
83
+ # Example:
84
+ #
85
+ # def create_recommended_lock( idobj )
86
+ # return DBITransactionLock.new( idobj.to_s )
87
+ # end
88
+ #
89
+ # == Authors
90
+ #
91
+ # * Michael Granger <ged@FaerieMUD.org>
92
+ #
93
+ # Please see the file LICENSE in the top-level directory for licensing details.
94
+ #
95
+ class Arrow::Session::Store < Arrow::Object
96
+ include PluginFactory
97
+ extend Forwardable
98
+
99
+ # The URI of the lock class recommended for use with this Store.
100
+ RecommendedLocker = URI.parse( 'file:.' )
101
+
102
+ # The methods which are delegate directly to the data hash.
103
+ DelegatedMethods = [
104
+ :[], :default, :default=, :each, :each_key, :each_pair, :each_value,
105
+ :empty?, :fetch, :has_key?, :has_value?, :include?, :index, :invert,
106
+ :keys, :length, :member?, :merge, :rehash, :reject, :select, :size,
107
+ :sort, :to_a, :value?, :values
108
+ ]
109
+
110
+
111
+ #############################################################
112
+ ### C L A S S M E T H O D S
113
+ #############################################################
114
+
115
+ ### Returns the Array of directories to search for derivatives; part of
116
+ ### the PluginFactory interface.
117
+ def self::derivativeDirs
118
+ [ 'arrow/session', 'arrow/session/store' ]
119
+ end
120
+
121
+
122
+ ### Overridden factory method: handle a URI object or a name
123
+ def self::create( uri, idobj )
124
+ uri = Arrow::Session.parse_uri( uri ) if uri.is_a?( String )
125
+ super( uri.scheme.dup, uri, idobj )
126
+ end
127
+
128
+
129
+
130
+ #############################################################
131
+ ### I N S T A N C E M E T H O D S
132
+ #############################################################
133
+
134
+ ### Create a new Arrow::Session::Store object.
135
+ def initialize( uri, idobj )
136
+ @data = {}
137
+ @id = idobj
138
+ @new = true
139
+ @modified = false
140
+
141
+ unless idobj.new?
142
+ self.retrieve
143
+ end
144
+
145
+ super()
146
+ end
147
+
148
+
149
+ ######
150
+ public
151
+ ######
152
+
153
+ # Delegate some methods to the data hash directly.
154
+ def_delegators :@data, *DelegatedMethods
155
+
156
+
157
+ # The raw session data hash
158
+ attr_reader :data
159
+
160
+
161
+ ### Set the +value+ for the specified +key+.
162
+ def []=( key, value )
163
+ @data[ key ] = value
164
+ @modified = true
165
+ end
166
+ alias_method :store, :[]=
167
+
168
+
169
+ ### Deletes and returns a key-value pair from the receiver whose key is
170
+ ### equal to +key+. If the +key+ is not found, returns the default
171
+ ### value. If the optional code-block is given and the key is not found,
172
+ ### the block is called with the key, and the return value is used as
173
+ ### the result of the method.
174
+ def delete( key, &block )
175
+ rval = @data.delete( key, &block )
176
+ return rval
177
+ ensure
178
+ @modified = true if rval != @data.default
179
+ end
180
+
181
+
182
+ ### Clear all key/value pairs from the store for this session.
183
+ def clear
184
+ @data.clear
185
+ @modified = true
186
+ end
187
+
188
+
189
+ ### Adds the contents of the +other+ hash to the session data,
190
+ ### overwriting entries in the session data with values from the +other+
191
+ ### hash where there are duplicates. If a +block+ is given, it is called
192
+ ### for each duplicate key, and the return value is the value set in the
193
+ ### hash.
194
+ def merge!( other, &block ) # :yields: key, sessionValue, otherValue
195
+ @data.merge!( other, &block )
196
+ ensure
197
+ @modified = true
198
+ end
199
+ alias_method :update, :merge!
200
+
201
+
202
+ ### Replace the contents of the session hash with those of the given
203
+ ### +other+ hash.
204
+ def replace( other )
205
+ @data.replace( other )
206
+ ensure
207
+ @modified = true
208
+ end
209
+
210
+
211
+ ### Deletes every key-value pair from the session data for which the
212
+ ### +block+ evaluates to true.
213
+ def reject!( &block ) # :yields: key, value
214
+ rval = @data.reject!( &block )
215
+ return rval
216
+ ensure
217
+ @modified = true if rval
218
+ end
219
+ alias_method :delete_if, :reject!
220
+
221
+
222
+ ### Returns +true+ if the receiver's data is out of sync with the
223
+ ### data in the backing store.
224
+ def modified?
225
+ @modified
226
+ end
227
+
228
+
229
+ ### Returns +true+ if the data in the receiver has not yet been saved to
230
+ ### the backing store, or if the entry in the backing store has been deleted
231
+ ### since it was last saved.
232
+ def new?
233
+ @new
234
+ end
235
+
236
+
237
+ ### Save the session data to the backing store
238
+ def save
239
+ return false unless self.modified? || self.new?
240
+ if self.new?
241
+ self.insert
242
+ else
243
+ self.update
244
+ end
245
+ end
246
+
247
+
248
+ ### Insert the current +data+ hash into whatever permanent storage the
249
+ ### Store object is acting as an interface to. Concrete implementations
250
+ ### should provide an overriding implementation of this method that
251
+ ### calls #super with a block which will be called with the serialized
252
+ ### data that should be stored.
253
+ def insert
254
+ self.log.debug "Inserting session data for key %s" % @id
255
+ yield( self.serialized_data )
256
+ @new = @modified = false
257
+ end
258
+
259
+
260
+ ### Update the current data hash stored in permanent storage with the
261
+ ### values contained in the store's data. Concrete implementations
262
+ ### should provide an overriding implementation of this method that
263
+ ### calls #super with a block which will be called with the serialized
264
+ ### data that should be stored.
265
+ def update
266
+ self.log.debug "Updating session data for key %s" % @id
267
+ yield( self.serialized_data )
268
+ @modified = false
269
+ end
270
+
271
+
272
+ ### Retrieve the data hash stored in permanent storage associated with
273
+ ### the id the object was created with. Concrete implementations
274
+ ### should provide an overriding implementation of this method that calls
275
+ ### #super with a block that returns the serialized data to be restored.
276
+ def retrieve
277
+ self.log.debug "Retrieving session data for key %s" % @id
278
+ self.serialized_data = yield
279
+ @new = @modified = false
280
+ end
281
+
282
+
283
+ ### Permanently remove the data hash associated with the id used in the
284
+ ### receiver's creation from permanent storage.
285
+ def remove
286
+ self.log.debug "Removing session data for key %s" % @id
287
+ @new = true
288
+ end
289
+
290
+
291
+ ### Returns an instance of the recommended lock object for the receiving
292
+ ### store. If no recommended locking strategy is known, this method
293
+ ### raises a SessionError.
294
+ def create_recommended_lock( idobj )
295
+ self.log.debug "Searching for recommended lock for %s" %
296
+ self.class.name
297
+
298
+ # Traverse the class hierarchy to find a class which defines a
299
+ # RecommendedLocker constant
300
+ adviceClass = self.class.ancestors.find {|klass|
301
+ klass.const_defined?( :RecommendedLocker )
302
+ } or raise SessionError, "No recommended locker for %p" %
303
+ self.class.ancestors
304
+
305
+ uri = adviceClass.const_get( :RecommendedLocker ) or
306
+ raise SessionError, "Could not fetch RecommendedLocker constant"
307
+
308
+ self.log.debug "Creating recommended lock %s" % uri
309
+ uri = Arrow::Session.parse_uri( uri ) if
310
+ uri.is_a?( String )
311
+
312
+ lock = Arrow::Session::Lock.create( uri, idobj )
313
+ self.log.debug "Created recommended lock object: %p" % lock
314
+
315
+ return lock
316
+ end
317
+
318
+
319
+ #########
320
+ protected
321
+ #########
322
+
323
+ ### Returns the data in the session store as a serialized object.
324
+ def serialized_data
325
+ data = strip_hash( @data )
326
+ return Marshal.dump( data )
327
+ end
328
+
329
+
330
+ ### Sets the session's data by deserializing the object
331
+ ### contained in the given +string+.
332
+ def serialized_data=( string )
333
+ if string.empty?
334
+ self.log.error "No session data: retaining default hash"
335
+ else
336
+ @data = Marshal.restore( string )
337
+ end
338
+ end
339
+
340
+
341
+
342
+ #######
343
+ private
344
+ #######
345
+
346
+ ### Return a copy of the given +hash+ with all non-serializable
347
+ ### objects stripped out of it.
348
+ def strip_hash( hash, cloned=true )
349
+ newhash = cloned ? hash.dup : hash
350
+ newhash.default = nil if newhash.default_proc
351
+ newhash.each_key {|key|
352
+ case newhash[ key ]
353
+ when Hash
354
+ newhash[ key ] = strip_hash( newhash[key], false )
355
+
356
+ when Proc, Method, UnboundMethod, IO
357
+ self.log.warning "Stripping unserializable object from session " \
358
+ "hash: %p" % newhash[ key ]
359
+ newhash[ key ] = "[Can't serialize a %s]" % newhash[ key ].class
360
+ end
361
+ }
362
+
363
+ return newhash
364
+ end
365
+
366
+ end # class Arrow::Session::Store