arrow 1.0.7
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.
- data/ChangeLog +1590 -0
- data/LICENSE +28 -0
- data/README +75 -0
- data/Rakefile +366 -0
- data/Rakefile.local +63 -0
- data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
- data/data/arrow/applets/args.rb +50 -0
- data/data/arrow/applets/config.rb +55 -0
- data/data/arrow/applets/error.rb +63 -0
- data/data/arrow/applets/files.rb +46 -0
- data/data/arrow/applets/inspect.rb +46 -0
- data/data/arrow/applets/nosuchapplet.rb +31 -0
- data/data/arrow/applets/status.rb +92 -0
- data/data/arrow/applets/test.rb +133 -0
- data/data/arrow/applets/tutorial/counter.rb +96 -0
- data/data/arrow/applets/tutorial/dingus.rb +67 -0
- data/data/arrow/applets/tutorial/hello.rb +34 -0
- data/data/arrow/applets/tutorial/hello2.rb +73 -0
- data/data/arrow/applets/tutorial/imgtext.rb +90 -0
- data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
- data/data/arrow/applets/tutorial/index.rb +36 -0
- data/data/arrow/applets/tutorial/logo.rb +98 -0
- data/data/arrow/applets/tutorial/memcache.rb +61 -0
- data/data/arrow/applets/tutorial/missing.rb +37 -0
- data/data/arrow/applets/tutorial/protected.rb +100 -0
- data/data/arrow/applets/tutorial/redirector.rb +52 -0
- data/data/arrow/applets/tutorial/rndimages.rb +159 -0
- data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
- data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
- data/data/arrow/applets/tutorial/superhello.rb +72 -0
- data/data/arrow/applets/tutorial/timeclock.rb +78 -0
- data/data/arrow/applets/view-applet.rb +123 -0
- data/data/arrow/applets/view-template.rb +85 -0
- data/data/arrow/applets/wiki.rb +274 -0
- data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
- data/data/arrow/templates/applet-status.tmpl +153 -0
- data/data/arrow/templates/args-display.tmpl +120 -0
- data/data/arrow/templates/config/display-table.tmpl +36 -0
- data/data/arrow/templates/config/display.tmpl +36 -0
- data/data/arrow/templates/counter-deleted.tmpl +33 -0
- data/data/arrow/templates/counter.tmpl +59 -0
- data/data/arrow/templates/dingus.tmpl +55 -0
- data/data/arrow/templates/enumtable.tmpl +8 -0
- data/data/arrow/templates/error-display.tmpl +92 -0
- data/data/arrow/templates/filemap.tmpl +89 -0
- data/data/arrow/templates/hello-world-src.tmpl +34 -0
- data/data/arrow/templates/hello-world.tmpl +60 -0
- data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
- data/data/arrow/templates/imgtext/form.tmpl +70 -0
- data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
- data/data/arrow/templates/imgtext/reload.tmpl +55 -0
- data/data/arrow/templates/inspect/display.tmpl +80 -0
- data/data/arrow/templates/loginform.tmpl +64 -0
- data/data/arrow/templates/logout.tmpl +32 -0
- data/data/arrow/templates/memcache/display.tmpl +41 -0
- data/data/arrow/templates/navbar.incl +27 -0
- data/data/arrow/templates/nosuchapplet.tmpl +32 -0
- data/data/arrow/templates/printsource.tmpl +35 -0
- data/data/arrow/templates/protected.tmpl +36 -0
- data/data/arrow/templates/rndimages.tmpl +38 -0
- data/data/arrow/templates/service-response.tmpl +13 -0
- data/data/arrow/templates/sharenotes/display.tmpl +38 -0
- data/data/arrow/templates/status.tmpl +120 -0
- data/data/arrow/templates/templateviewer.tmpl +43 -0
- data/data/arrow/templates/test/harness.tmpl +57 -0
- data/data/arrow/templates/test/list.tmpl +48 -0
- data/data/arrow/templates/test/problem.tmpl +42 -0
- data/data/arrow/templates/tutorial/index.tmpl +37 -0
- data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
- data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
- data/data/arrow/templates/view-applet.tmpl +40 -0
- data/data/arrow/templates/view-template.tmpl +83 -0
- data/data/arrow/templates/wiki/formerror.tmpl +47 -0
- data/data/arrow/templates/wiki/markup_help.incl +6 -0
- data/data/arrow/templates/wiki/new.tmpl +56 -0
- data/data/arrow/templates/wiki/new_system.tmpl +122 -0
- data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
- data/data/arrow/templates/wiki/show.tmpl +34 -0
- data/docs/manual/layouts/default.page +43 -0
- data/docs/manual/lib/api-filter.rb +81 -0
- data/docs/manual/lib/editorial-filter.rb +64 -0
- data/docs/manual/lib/examples-filter.rb +244 -0
- data/docs/manual/lib/links-filter.rb +117 -0
- data/lib/apache/fakerequest.rb +448 -0
- data/lib/apache/logger.rb +33 -0
- data/lib/arrow.rb +51 -0
- data/lib/arrow/acceptparam.rb +207 -0
- data/lib/arrow/applet.rb +725 -0
- data/lib/arrow/appletmixins.rb +218 -0
- data/lib/arrow/appletregistry.rb +590 -0
- data/lib/arrow/applettestcase.rb +503 -0
- data/lib/arrow/broker.rb +255 -0
- data/lib/arrow/cache.rb +176 -0
- data/lib/arrow/config-loaders/yaml.rb +75 -0
- data/lib/arrow/config.rb +615 -0
- data/lib/arrow/constants.rb +24 -0
- data/lib/arrow/cookie.rb +359 -0
- data/lib/arrow/cookieset.rb +108 -0
- data/lib/arrow/dispatcher.rb +368 -0
- data/lib/arrow/dispatcherloader.rb +50 -0
- data/lib/arrow/exceptions.rb +61 -0
- data/lib/arrow/fallbackhandler.rb +48 -0
- data/lib/arrow/formvalidator.rb +631 -0
- data/lib/arrow/htmltokenizer.rb +343 -0
- data/lib/arrow/logger.rb +488 -0
- data/lib/arrow/logger/apacheoutputter.rb +69 -0
- data/lib/arrow/logger/arrayoutputter.rb +63 -0
- data/lib/arrow/logger/coloroutputter.rb +111 -0
- data/lib/arrow/logger/fileoutputter.rb +96 -0
- data/lib/arrow/logger/htmloutputter.rb +54 -0
- data/lib/arrow/logger/outputter.rb +123 -0
- data/lib/arrow/mixins.rb +425 -0
- data/lib/arrow/monkeypatches.rb +94 -0
- data/lib/arrow/object.rb +117 -0
- data/lib/arrow/path.rb +196 -0
- data/lib/arrow/service.rb +447 -0
- data/lib/arrow/session.rb +289 -0
- data/lib/arrow/session/dbstore.rb +100 -0
- data/lib/arrow/session/filelock.rb +160 -0
- data/lib/arrow/session/filestore.rb +132 -0
- data/lib/arrow/session/id.rb +98 -0
- data/lib/arrow/session/lock.rb +253 -0
- data/lib/arrow/session/md5id.rb +42 -0
- data/lib/arrow/session/nulllock.rb +42 -0
- data/lib/arrow/session/posixlock.rb +166 -0
- data/lib/arrow/session/sha1id.rb +54 -0
- data/lib/arrow/session/store.rb +366 -0
- data/lib/arrow/session/usertrackid.rb +52 -0
- data/lib/arrow/spechelpers.rb +73 -0
- data/lib/arrow/template.rb +713 -0
- data/lib/arrow/template/attr.rb +31 -0
- data/lib/arrow/template/call.rb +31 -0
- data/lib/arrow/template/comment.rb +33 -0
- data/lib/arrow/template/container.rb +118 -0
- data/lib/arrow/template/else.rb +41 -0
- data/lib/arrow/template/elsif.rb +44 -0
- data/lib/arrow/template/escape.rb +53 -0
- data/lib/arrow/template/export.rb +87 -0
- data/lib/arrow/template/for.rb +145 -0
- data/lib/arrow/template/if.rb +78 -0
- data/lib/arrow/template/import.rb +119 -0
- data/lib/arrow/template/include.rb +206 -0
- data/lib/arrow/template/iterator.rb +208 -0
- data/lib/arrow/template/nodes.rb +734 -0
- data/lib/arrow/template/parser.rb +571 -0
- data/lib/arrow/template/prettyprint.rb +53 -0
- data/lib/arrow/template/render.rb +191 -0
- data/lib/arrow/template/selectlist.rb +94 -0
- data/lib/arrow/template/set.rb +87 -0
- data/lib/arrow/template/timedelta.rb +81 -0
- data/lib/arrow/template/unless.rb +78 -0
- data/lib/arrow/template/urlencode.rb +51 -0
- data/lib/arrow/template/yield.rb +139 -0
- data/lib/arrow/templatefactory.rb +125 -0
- data/lib/arrow/testcase.rb +567 -0
- data/lib/arrow/transaction.rb +608 -0
- data/rake/191_compat.rb +26 -0
- data/rake/dependencies.rb +76 -0
- data/rake/documentation.rb +114 -0
- data/rake/helpers.rb +502 -0
- data/rake/hg.rb +282 -0
- data/rake/manual.rb +787 -0
- data/rake/packaging.rb +129 -0
- data/rake/publishing.rb +278 -0
- data/rake/style.rb +62 -0
- data/rake/svn.rb +668 -0
- data/rake/testing.rb +187 -0
- data/rake/verifytask.rb +64 -0
- data/spec/arrow/acceptparam_spec.rb +157 -0
- data/spec/arrow/applet_spec.rb +575 -0
- data/spec/arrow/appletmixins_spec.rb +409 -0
- data/spec/arrow/appletregistry_spec.rb +294 -0
- data/spec/arrow/broker_spec.rb +153 -0
- data/spec/arrow/config_spec.rb +224 -0
- data/spec/arrow/cookieset_spec.rb +164 -0
- data/spec/arrow/dispatcher_spec.rb +137 -0
- data/spec/arrow/dispatcherloader_spec.rb +65 -0
- data/spec/arrow/formvalidator_spec.rb +781 -0
- data/spec/arrow/logger_spec.rb +346 -0
- data/spec/arrow/mixins_spec.rb +120 -0
- data/spec/arrow/service_spec.rb +645 -0
- data/spec/arrow/session_spec.rb +121 -0
- data/spec/arrow/template/iterator_spec.rb +222 -0
- data/spec/arrow/templatefactory_spec.rb +185 -0
- data/spec/arrow/transaction_spec.rb +319 -0
- data/spec/arrow_spec.rb +37 -0
- data/spec/lib/appletmatchers.rb +281 -0
- data/spec/lib/constants.rb +77 -0
- data/spec/lib/helpers.rb +41 -0
- data/spec/lib/matchers.rb +44 -0
- data/tests/cookie.tests.rb +310 -0
- data/tests/path.tests.rb +157 -0
- data/tests/session.tests.rb +111 -0
- data/tests/session_id.tests.rb +82 -0
- data/tests/session_lock.tests.rb +191 -0
- data/tests/session_store.tests.rb +53 -0
- data/tests/template.tests.rb +1360 -0
- metadata +339 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
require 'arrow/applet'
|
|
4
|
+
require 'arrow/acceptparam'
|
|
5
|
+
|
|
6
|
+
# :nodoc:
|
|
7
|
+
module Arrow
|
|
8
|
+
|
|
9
|
+
# A collection of functions for abstracting authentication and authorization
|
|
10
|
+
# away from Arrow::Applets. Applets which include this module should provide
|
|
11
|
+
# implementations of at least the #get_authenticated_user method, and may
|
|
12
|
+
# provide implementations of other methods to tailor the authentication for
|
|
13
|
+
# their particular applet.
|
|
14
|
+
#
|
|
15
|
+
# == Customization API
|
|
16
|
+
#
|
|
17
|
+
# [[#get_authenticated_user]]
|
|
18
|
+
# Override this method to provide the particulars of your authentication
|
|
19
|
+
# system. The method is given the Arrow::Transaction object that wraps the
|
|
20
|
+
# incoming request, and should return whatever kind of "user" object they
|
|
21
|
+
# wish to use. The only requirement for a user object as far as this mixin
|
|
22
|
+
# is concerned is that it must have a #to_s method, so even a simple username
|
|
23
|
+
# in a String will suffice. If no authorization is possible, return nil, which
|
|
24
|
+
# will cause the #login_action to be invoked.
|
|
25
|
+
# [[#user_is_authorized]]
|
|
26
|
+
# Override this method to provide authorization checks of an authenticated user
|
|
27
|
+
# (the one returned from #get_authenticated_user) against the incoming request.
|
|
28
|
+
# If the user is authorized to run the action, return +true+, else return
|
|
29
|
+
# +false+. Failed authorization will cause the #deny_access_action to be
|
|
30
|
+
# invoked.
|
|
31
|
+
# [[#login_action]]
|
|
32
|
+
# Override this method if you wish to customize the login process. By default,
|
|
33
|
+
# this returns a response that prompts the client using Basic HTTP
|
|
34
|
+
# authentication.
|
|
35
|
+
# [[#logout_action]]
|
|
36
|
+
# Override this method if you wish to customize the logout process. By default,
|
|
37
|
+
# this declines the request, which will tell Apache to try to handle the
|
|
38
|
+
# request itself.
|
|
39
|
+
# [[#deny_access_action]]
|
|
40
|
+
# Override this method if you wish to customize what happens when the client
|
|
41
|
+
# sends a request for a resource they are not authorized to interact with. By
|
|
42
|
+
# default, this method returns a simple HTTP FORBIDDEN response.
|
|
43
|
+
#
|
|
44
|
+
# == VCS Id
|
|
45
|
+
#
|
|
46
|
+
# $Id$
|
|
47
|
+
#
|
|
48
|
+
# == Authors
|
|
49
|
+
#
|
|
50
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
|
51
|
+
#
|
|
52
|
+
# :include: LICENSE
|
|
53
|
+
#
|
|
54
|
+
#--
|
|
55
|
+
#
|
|
56
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
|
57
|
+
#
|
|
58
|
+
module AppletAuthentication
|
|
59
|
+
|
|
60
|
+
### Default AppletAuthentication API: provides login functionality for actions that
|
|
61
|
+
### require authorization; override this to provide a login form. By default, this
|
|
62
|
+
### just returns an HTTP UNAUTHORIZED response.
|
|
63
|
+
def login_action( txn, *args )
|
|
64
|
+
self.log.info "Prompting the client for authentication"
|
|
65
|
+
# :TODO: This really needs to set the WWW-Authenticate header...
|
|
66
|
+
txn.status = Apache::HTTP_UNAUTHORIZED
|
|
67
|
+
return "this resource requires authentication"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
### Default AppletAuthentication API: provides login functionality for actions that
|
|
72
|
+
### require authorization; override this to customize the logout process. By default, this
|
|
73
|
+
### just returns +nil+, which will decline the request.
|
|
74
|
+
def logout_action( txn, *args )
|
|
75
|
+
self.log.info "No logout action provided, passing the request off to the server"
|
|
76
|
+
return Apache::DECLINED
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
### Default AppletAuthentication API: provides a hook for applets which have some
|
|
81
|
+
### actions which require authorization to run; override this to provide a "Forbidden"
|
|
82
|
+
### page. By default, this just returns an HTTP FORBIDDEN response.
|
|
83
|
+
def deny_access_action( txn, *args )
|
|
84
|
+
self.log.error "Unauthorized request for %s" % [ txn.uri ]
|
|
85
|
+
txn.status = Apache::FORBIDDEN
|
|
86
|
+
return "access denied"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
#########
|
|
91
|
+
protected
|
|
92
|
+
#########
|
|
93
|
+
|
|
94
|
+
### Check to see that the user is authenticated. If not attempt to
|
|
95
|
+
### authenticate them via a form. If they are authenticated, or become
|
|
96
|
+
### authenticated after the form action, call the supplied block with the
|
|
97
|
+
### authenticated user.
|
|
98
|
+
def with_authentication( txn, *args )
|
|
99
|
+
self.log.debug "wrapping a block in authentication"
|
|
100
|
+
|
|
101
|
+
# If the user doesn't have a session user, go to the login form.
|
|
102
|
+
if user = self.get_authenticated_user( txn )
|
|
103
|
+
return yield( user )
|
|
104
|
+
else
|
|
105
|
+
self.log.warning "Authentication failed from %s for %s" %
|
|
106
|
+
[ txn.remote_host(Apache::REMOTE_NOLOOKUP), txn.the_request ]
|
|
107
|
+
return self.subrun( :login, txn, *args )
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
### Wrap a block in authorization. If the given +user+ has all of the
|
|
113
|
+
### necessary permissions to run the given +applet_chain+ (an Array of
|
|
114
|
+
### Arrow::AppRegistry::ChainLink structs), call the provided block.
|
|
115
|
+
### Otherwise run the 'deny_access' action and return the result.
|
|
116
|
+
def with_authorization( txn, *args )
|
|
117
|
+
self.with_authentication( txn ) do |user|
|
|
118
|
+
self.log.debug "Checking permissions of '%s' to execute %s" % [ user, txn.uri ]
|
|
119
|
+
|
|
120
|
+
if self.user_is_authorized( user, txn, *args )
|
|
121
|
+
return yield
|
|
122
|
+
else
|
|
123
|
+
self.log.warning "Access denied to %s for %s" % [ user, txn.the_request ]
|
|
124
|
+
return self.subrun( :deny_access, txn )
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
### Default AppletAuthentication API: return a "user" object if the specified +txn+
|
|
131
|
+
### object provides authentication. Applets wishing to authenticate uses should
|
|
132
|
+
### provide an overriding implementation of this method. The base implementation
|
|
133
|
+
### always returns +nil+.
|
|
134
|
+
def get_authenticated_user( txn )
|
|
135
|
+
self.log.notice "No implementation of get_authenticated_user for %s" %
|
|
136
|
+
[ self.class.signature.name ]
|
|
137
|
+
return nil
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
### Default AppletAuthentication API: returns true if the specified +user+ is
|
|
142
|
+
### authorized to run the applet. Applets wishing to authorize users should
|
|
143
|
+
### provide an overriding implementation of this method. The base implementation
|
|
144
|
+
### always returns +false+.
|
|
145
|
+
def user_is_authorized( user, txn, *args )
|
|
146
|
+
self.log.notice "No implementation of user_is_authorized for %s" %
|
|
147
|
+
[ self.class.signature.name ]
|
|
148
|
+
return false
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
end # module AppletAuthentication
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
### Add access-control to all actions and then allow them to be removed on a per-action
|
|
156
|
+
### basis via a directive.
|
|
157
|
+
module AccessControls
|
|
158
|
+
include Arrow::AppletAuthentication
|
|
159
|
+
|
|
160
|
+
# Actions which don't go through access control
|
|
161
|
+
UNAUTHENTICATED_ACTIONS = [
|
|
162
|
+
:deny_access, :login, :logout
|
|
163
|
+
].freeze
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
### Methods to add to including classes
|
|
167
|
+
module ClassMethods
|
|
168
|
+
### Allow declaration of actions which don't require authentication -- all other
|
|
169
|
+
### methods are authenticated by default
|
|
170
|
+
def unauthenticated_actions( *actions )
|
|
171
|
+
@unauthenticated_actions.push( *actions )
|
|
172
|
+
return @unauthenticated_actions
|
|
173
|
+
end
|
|
174
|
+
alias :unauthenticated_action :unauthenticated_actions
|
|
175
|
+
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
### Inclusion callback
|
|
180
|
+
def self::included( mod )
|
|
181
|
+
Arrow::Logger[ self ].debug "Adding declarative method to %p" % [ mod ]
|
|
182
|
+
mod.instance_variable_set( :@unauthenticated_actions, UNAUTHENTICATED_ACTIONS.dup )
|
|
183
|
+
mod.extend( ClassMethods )
|
|
184
|
+
super
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
### Overridden to map the +action+ to the authorization action's method if
|
|
189
|
+
### +action+ isn't one of the ones that's defined as unauthenticated.
|
|
190
|
+
def find_action_method( txn, action=nil, *args )
|
|
191
|
+
if self.class.unauthenticated_actions.include?( action )
|
|
192
|
+
self.log.debug "Supering to unauthenticated action %p" % [ action ]
|
|
193
|
+
super
|
|
194
|
+
else
|
|
195
|
+
self.log.debug "Action %p wasn't marked as unauthenticated; checking authorization." %
|
|
196
|
+
[ action ]
|
|
197
|
+
with_authorization( txn, action, *args ) do
|
|
198
|
+
super
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
### Delegate to applets further on in the chain only if the user is authorized.
|
|
205
|
+
def delegate( txn, chain, *args )
|
|
206
|
+
self.log.debug "Delegating to chain: %p" % [ chain ]
|
|
207
|
+
|
|
208
|
+
with_authorization( txn, chain ) do
|
|
209
|
+
yield( chain )
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
end # AccessControls
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
end # module Arrow
|
|
217
|
+
|
|
218
|
+
|
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'arrow/broker'
|
|
4
|
+
require 'forwardable'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# The Arrow::AppletRegistry class, a derivative of
|
|
9
|
+
# Arrow::Object. Instances of this class are responsible for loading and
|
|
10
|
+
# maintaining the collection of Arrow::Applets registered with an
|
|
11
|
+
# Arrow::Broker.
|
|
12
|
+
#
|
|
13
|
+
# == VCS Id
|
|
14
|
+
#
|
|
15
|
+
# $Id$
|
|
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::AppletRegistry < Arrow::Object
|
|
24
|
+
extend Forwardable
|
|
25
|
+
include Enumerable, Arrow::Loggable
|
|
26
|
+
|
|
27
|
+
# Pattern for matching valid components of the uri
|
|
28
|
+
IDENTIFIER = /^\w[-\w]*/
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Link in an applet chain
|
|
32
|
+
ChainLink = Struct.new( 'ArrowAppletChainLink', :applet, :path, :args )
|
|
33
|
+
|
|
34
|
+
### Registry applet filemap data structure.
|
|
35
|
+
class AppletFile < Arrow::Object
|
|
36
|
+
|
|
37
|
+
DEFAULT_SOURCE_WINDOW_SIZE = 20
|
|
38
|
+
|
|
39
|
+
### Create a new Arrow::AppletRegistry::AppletFile for the applet at
|
|
40
|
+
### the given +path+.
|
|
41
|
+
def initialize( path )
|
|
42
|
+
@path = path
|
|
43
|
+
@uris = []
|
|
44
|
+
@appletclasses = nil
|
|
45
|
+
@timestamp = File.mtime( path )
|
|
46
|
+
@exception = nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
######
|
|
51
|
+
public
|
|
52
|
+
######
|
|
53
|
+
|
|
54
|
+
# The fully-qualified path to the applet file
|
|
55
|
+
attr_reader :path
|
|
56
|
+
|
|
57
|
+
# An Array of URIs that applets contained in this file are mapped to
|
|
58
|
+
attr_reader :uris
|
|
59
|
+
|
|
60
|
+
# A Time object representing the modification time of the file when it was loaded
|
|
61
|
+
attr_reader :timestamp
|
|
62
|
+
|
|
63
|
+
# The Exception object that was thrown when trying to load this file, if any
|
|
64
|
+
attr_accessor :exception
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
### Returns +true+ if this file loaded without error
|
|
68
|
+
def loaded_okay?
|
|
69
|
+
@exception.nil?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
### Returns +true+ if the corresponding file has changed since it was loaded
|
|
74
|
+
def has_changed?
|
|
75
|
+
@timestamp != File.mtime( path )
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
### Returns an Array of Arrow::Applet classes loaded from this file, loading
|
|
80
|
+
### them if they haven't already been loaded.
|
|
81
|
+
def appletclasses
|
|
82
|
+
unless @appletclasses
|
|
83
|
+
self.log.debug "Loading applet classes from #{@path}"
|
|
84
|
+
@appletclasses = Arrow::Applet.load( @path )
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
rescue ::Exception => err
|
|
88
|
+
@exception = err
|
|
89
|
+
frames = self.filtered_backtrace
|
|
90
|
+
self.log.error "%s failed to load: %s" % [ path, err.message ]
|
|
91
|
+
self.log.debug " " + frames.collect {|frame| "[%s]" % frame}.join(" ")
|
|
92
|
+
@appletclasses = []
|
|
93
|
+
ensure
|
|
94
|
+
return @appletclasses
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
### Return the lines of the applet exception's backtrace up to the
|
|
99
|
+
### first frame of the framework. Returns an empty Array if there is
|
|
100
|
+
### no current exception.
|
|
101
|
+
def filtered_backtrace
|
|
102
|
+
return [] unless @exception
|
|
103
|
+
|
|
104
|
+
filtered = []
|
|
105
|
+
@exception.backtrace.each do |frame|
|
|
106
|
+
break if frame.include?('lib/arrow/')
|
|
107
|
+
filtered.push( frame )
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
return filtered
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
### Return the lines from the applet's source as an Array.
|
|
115
|
+
def source_lines
|
|
116
|
+
return File.readlines( @path )
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
### Return +window_size+ lines of the source from the applet
|
|
121
|
+
### surrounding the specified +linenum+ as an Array of Hashes of the
|
|
122
|
+
### form:
|
|
123
|
+
### {
|
|
124
|
+
### :source => <line of source code>,
|
|
125
|
+
### :linenum => <line number>,
|
|
126
|
+
### :target => <true if this is the target line>
|
|
127
|
+
### }
|
|
128
|
+
def source_window( linenum, window_size=DEFAULT_SOURCE_WINDOW_SIZE )
|
|
129
|
+
linenum -= 1
|
|
130
|
+
before_line = linenum - (window_size / 2)
|
|
131
|
+
after_line = linenum + (window_size / 2.0).ceil
|
|
132
|
+
|
|
133
|
+
before_line = 0 if before_line < 0
|
|
134
|
+
|
|
135
|
+
self.log.debug "Reading lines %d-%d from %s for source window on line %d" %
|
|
136
|
+
[ before_line, after_line, @path, linenum + 1 ]
|
|
137
|
+
|
|
138
|
+
rval = []
|
|
139
|
+
lines = self.source_lines[ before_line .. after_line ]
|
|
140
|
+
lines.each_with_index do |line, i|
|
|
141
|
+
rval << {
|
|
142
|
+
:source => line.chomp,
|
|
143
|
+
:linenum => before_line + i + 1,
|
|
144
|
+
:target => (before_line + i) == linenum,
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
return rval
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
### Return the line of the exception that occurred while loading the
|
|
153
|
+
### applet, if any. If there was no exception, this method returns
|
|
154
|
+
### +nil+.
|
|
155
|
+
def exception_line
|
|
156
|
+
return nil unless @exception
|
|
157
|
+
targetline = nil
|
|
158
|
+
line = nil
|
|
159
|
+
|
|
160
|
+
# ScriptErrors have the target line in the message; everything else
|
|
161
|
+
# is assumed to have it in the first line of the backtrace
|
|
162
|
+
if @exception.is_a?( ScriptError )
|
|
163
|
+
targetline = @exception.message
|
|
164
|
+
else
|
|
165
|
+
targetline = @exception.backtrace.first
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
#
|
|
169
|
+
if targetline =~ /.*:(\d+)(?::.*)?$/
|
|
170
|
+
line = Integer( $1 )
|
|
171
|
+
else
|
|
172
|
+
raise "Couldn't parse exception backtrace '%s' for error line." %
|
|
173
|
+
[ targetline ]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
return line
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
### Return +window_size+ lines surrounding the line of the applet's
|
|
181
|
+
### loading exception. If there was no loading exception, returns
|
|
182
|
+
### an empty Array.
|
|
183
|
+
def exception_source_window( window_size=DEFAULT_SOURCE_WINDOW_SIZE )
|
|
184
|
+
return [] unless @exception
|
|
185
|
+
return self.source_window( self.exception_line, window_size )
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
#################################################################
|
|
192
|
+
### C L A S S M E T H O D S
|
|
193
|
+
#################################################################
|
|
194
|
+
|
|
195
|
+
### Get the 'gem home' from RubyGems and check it for sanity. Returns +nil+ if it
|
|
196
|
+
### is not an extant, non-world-writable directory.
|
|
197
|
+
def self::get_safe_gemhome
|
|
198
|
+
gemhome = Pathname.new( Gem.user_home ) + 'gems'
|
|
199
|
+
gemhome.untaint
|
|
200
|
+
|
|
201
|
+
if ! gemhome.directory?
|
|
202
|
+
Arrow::Logger[ self ].notice "Gem home '%s' is not a directory; ignoring it" % [ gemhome ]
|
|
203
|
+
return nil
|
|
204
|
+
elsif (gemhome.stat.mode & 0002).nonzero?
|
|
205
|
+
Arrow::Logger[ self ].notice "Gem home '%s' is world-writable; ignoring it" % [ gemhome ]
|
|
206
|
+
return nil
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
Arrow::Logger[ self ].info "Got safe gem home: %p" % [ gemhome ]
|
|
210
|
+
return gemhome
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
#################################################################
|
|
217
|
+
### I N S T A N C E M E T H O D S
|
|
218
|
+
#################################################################
|
|
219
|
+
|
|
220
|
+
# The stuff the registry needs:
|
|
221
|
+
#
|
|
222
|
+
# * Map of uri to applet object [maps incoming requests to applet/s]
|
|
223
|
+
# * Map of file to uri/s [for deleting entries from map of uris when a file disappears]
|
|
224
|
+
|
|
225
|
+
### Create a new Arrow::AppletRegistry object.
|
|
226
|
+
def initialize( config )
|
|
227
|
+
@config = config
|
|
228
|
+
|
|
229
|
+
@path = @config.applets.path
|
|
230
|
+
@classmap = nil
|
|
231
|
+
@filemap = {}
|
|
232
|
+
@urispace = {}
|
|
233
|
+
@template_factory = Arrow::TemplateFactory.new( config )
|
|
234
|
+
@load_time = nil
|
|
235
|
+
|
|
236
|
+
self.load_gems
|
|
237
|
+
self.load_applets
|
|
238
|
+
|
|
239
|
+
super()
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
### Copy initializer -- reload applets for cloned registries.
|
|
244
|
+
def initialize_copy( other ) # :nodoc:
|
|
245
|
+
@config = other.config.dup
|
|
246
|
+
|
|
247
|
+
@path = @config.applets.path.dup
|
|
248
|
+
@classmap = nil
|
|
249
|
+
@filemap = {}
|
|
250
|
+
@urispace = {}
|
|
251
|
+
@template_factory = Arrow::TemplateFactory.new( config )
|
|
252
|
+
@load_time = nil
|
|
253
|
+
|
|
254
|
+
self.load_gems
|
|
255
|
+
self.load_applets
|
|
256
|
+
|
|
257
|
+
super
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
######
|
|
262
|
+
public
|
|
263
|
+
######
|
|
264
|
+
|
|
265
|
+
# The internal hash of Entry objects, keyed by URI
|
|
266
|
+
attr_reader :urispace
|
|
267
|
+
|
|
268
|
+
# The internal hash of Entry objects keyed by the file they were loaded
|
|
269
|
+
# from
|
|
270
|
+
attr_reader :filemap
|
|
271
|
+
|
|
272
|
+
# The Arrow::Config object which specified the registry's behavior.
|
|
273
|
+
attr_reader :config
|
|
274
|
+
|
|
275
|
+
# The Arrow::TemplateFactory which will be given to any loaded applet
|
|
276
|
+
attr_reader :template_factory
|
|
277
|
+
|
|
278
|
+
# The Time when the registry was last loaded
|
|
279
|
+
attr_accessor :load_time
|
|
280
|
+
|
|
281
|
+
# The path the registry will search when looking for new/updated/deleted applets
|
|
282
|
+
attr_reader :path
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# Delegate hash-ish methods to the uri-keyed internal hash
|
|
286
|
+
def_delegators :@urispace, :[], :[]=, :key?, :keys, :length, :each
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
### Check the config for any gems to load, load them, and add their template and applet
|
|
290
|
+
### directories to the appropriate parts of the config.
|
|
291
|
+
def load_gems
|
|
292
|
+
self.log.info "Loading gems."
|
|
293
|
+
|
|
294
|
+
unless @config.respond_to?( :gems )
|
|
295
|
+
self.log.debug "No gems section in the config; skipping gemified applets"
|
|
296
|
+
return
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
self.log.debug " using gem config: %p" % [ config.gems ]
|
|
300
|
+
|
|
301
|
+
# Make sure the 'gem home' is a directory and not world-writable; don't use it
|
|
302
|
+
# otherwise
|
|
303
|
+
gemhome = self.class.get_safe_gemhome
|
|
304
|
+
paths = @config.gems.path.collect {|path| path.untaint }
|
|
305
|
+
self.log.debug " safe gem paths: %p" % [ paths ]
|
|
306
|
+
Gem.use_paths( Apache.server_root, paths )
|
|
307
|
+
|
|
308
|
+
@config.gems.applets.to_h.each do |gemname, reqstring|
|
|
309
|
+
self.log.debug " trying to load %s %s" % [ gemname, reqstring ]
|
|
310
|
+
reqstring = '>= 0' if reqstring.nil? or reqstring.empty?
|
|
311
|
+
|
|
312
|
+
begin
|
|
313
|
+
self.log.info "Activating gem %s (%s)" % [ gemname, reqstring ]
|
|
314
|
+
Gem.activate( gemname.to_s, reqstring )
|
|
315
|
+
self.log.info " gem %s activated." % [ gemname ]
|
|
316
|
+
rescue LoadError => err
|
|
317
|
+
self.log.crit "%s while activating '%s': %s" %
|
|
318
|
+
[ err.class.name, gemname, err.message ]
|
|
319
|
+
err.backtrace.each do |frame|
|
|
320
|
+
self.log.debug " " + frame
|
|
321
|
+
end
|
|
322
|
+
else
|
|
323
|
+
datadir = Pathname.new( Gem.datadir(gemname.to_s) )
|
|
324
|
+
appletdir = datadir + 'applets'
|
|
325
|
+
templatedir = datadir + 'templates'
|
|
326
|
+
self.log.debug "Adding appletdir %p and templatedir %p" %
|
|
327
|
+
[ appletdir, templatedir ]
|
|
328
|
+
@path << appletdir.to_s
|
|
329
|
+
@template_factory.path << templatedir.to_s
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
self.log.info " done loading gems (path is now: %p)." % [ @path ]
|
|
334
|
+
end
|
|
335
|
+
alias_method :reload_gems, :load_gems
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
### Loading and reloading the applet registy uses the following strategy:
|
|
339
|
+
###
|
|
340
|
+
### 1. Find all files in the config.applets.path matching the
|
|
341
|
+
### config.applets.pattern.
|
|
342
|
+
### 2. Remove from the registry any loaded applets which correspond to
|
|
343
|
+
### applet files which are no longer in that list.
|
|
344
|
+
### 3. For files which do exist in the path which were already loaded,
|
|
345
|
+
### reload applets for any whose timestamp has changed since the applets
|
|
346
|
+
### were loaded.
|
|
347
|
+
### 4. For new files, load the applets they contain.
|
|
348
|
+
|
|
349
|
+
### Load any new applets in the registry's path, reload any previously-
|
|
350
|
+
### loaded applets whose files have changed, and discard any applets whose
|
|
351
|
+
### files have disappeared.
|
|
352
|
+
def load_applets
|
|
353
|
+
self.log.debug "Loading applet registry"
|
|
354
|
+
|
|
355
|
+
@classmap = self.build_classmap
|
|
356
|
+
filelist = self.find_appletfiles
|
|
357
|
+
|
|
358
|
+
# Remove applet files which correspond to files that are no longer
|
|
359
|
+
# in the list
|
|
360
|
+
self.purge_deleted_applets( @filemap.keys - filelist ) unless
|
|
361
|
+
@filemap.empty?
|
|
362
|
+
|
|
363
|
+
# Now search the applet path for applet files
|
|
364
|
+
filelist.each do |appletfile|
|
|
365
|
+
self.log.debug "Found applet file %p" % appletfile
|
|
366
|
+
self.load_applets_from_file( appletfile )
|
|
367
|
+
self.log.debug "After %s, registry has %d entries" %
|
|
368
|
+
[ appletfile, @urispace.length ]
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
self.load_time = Time.now
|
|
372
|
+
end
|
|
373
|
+
alias_method :reload_applets, :load_applets
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
### Find the chain of applets indicated by the given +uri+ and return an
|
|
377
|
+
### Array of ChainLink structs.
|
|
378
|
+
def find_applet_chain( uri )
|
|
379
|
+
self.log.debug "Searching urispace for appletchain for %p" % [ uri ]
|
|
380
|
+
|
|
381
|
+
uri_parts = uri.sub(%r{^/(?=.)}, '').split(%r{/}).grep( IDENTIFIER )
|
|
382
|
+
appletchain = []
|
|
383
|
+
args = []
|
|
384
|
+
|
|
385
|
+
# If there's an applet installed at the base, prepend it to the
|
|
386
|
+
# appletchain
|
|
387
|
+
if @urispace.key?( "" )
|
|
388
|
+
appletchain << ChainLink.new( @urispace[""], "", uri_parts )
|
|
389
|
+
self.log.debug "Added base applet to chain."
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Only allow reference to internal handlers (handlers mapped to
|
|
393
|
+
# directories that start with '_') if allow_internal is set.
|
|
394
|
+
self.log.debug "Split URI into parts: %p" % [uri_parts]
|
|
395
|
+
|
|
396
|
+
# Map uri fragments onto registry entries, stopping at any element
|
|
397
|
+
# which isn't a valid Ruby identifier.
|
|
398
|
+
uri_parts.each_index do |i|
|
|
399
|
+
newuri = uri_parts[0,i+1].join("/")
|
|
400
|
+
# self.log.debug "Testing %s against %p" % [ newuri, @urispace.keys.sort ]
|
|
401
|
+
appletchain << ChainLink.new( @urispace[newuri], newuri, uri_parts[(i+1)..-1] ) if
|
|
402
|
+
@urispace.key?( newuri )
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
return appletchain
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
### Check the applets path for new/updated/deleted applets if the poll
|
|
410
|
+
### interval has passed.
|
|
411
|
+
def check_for_updates
|
|
412
|
+
interval = @config.applets.pollInterval
|
|
413
|
+
if interval.nonzero?
|
|
414
|
+
if Time.now - self.load_time > interval
|
|
415
|
+
self.log.debug "Checking for applet updates: poll interval at %ds" % [ interval ]
|
|
416
|
+
self.reload_applets
|
|
417
|
+
self.reload_gems
|
|
418
|
+
end
|
|
419
|
+
else
|
|
420
|
+
self.log.debug "Dynamic applet reloading turned off, continuing"
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
### Remove the applets that were loaded from the given +missing_files+ from
|
|
426
|
+
### the registry.
|
|
427
|
+
def purge_deleted_applets( *missing_files )
|
|
428
|
+
|
|
429
|
+
# For each filename, find the applets which were loaded from it,
|
|
430
|
+
# map the name of each applet to a uri via the classmap, and delete
|
|
431
|
+
# the entries by uri
|
|
432
|
+
missing_files.flatten.each do |filename|
|
|
433
|
+
self.log.info "Unregistering old applets from %p" % [ filename ]
|
|
434
|
+
|
|
435
|
+
@filemap[ filename ].uris.each do |uri|
|
|
436
|
+
self.log.debug " Removing %p, registered at %p" % [ @urispace[uri], uri ]
|
|
437
|
+
@urispace.delete( uri )
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
@filemap.delete( filename )
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
### Load the applet classes from the given +path+ and return them
|
|
446
|
+
### in an Array. If a block is given, then each loaded class is yielded
|
|
447
|
+
### to the block in turn, and the return values are used in the Array
|
|
448
|
+
### instead.
|
|
449
|
+
def load_applets_from_file( path )
|
|
450
|
+
|
|
451
|
+
# Reload mode -- don't do anything unless the file's been updated
|
|
452
|
+
if @filemap.key?( path )
|
|
453
|
+
file = @filemap[ path ]
|
|
454
|
+
|
|
455
|
+
if file.has_changed?
|
|
456
|
+
self.log.info "File %p has changed since loaded. Reloading." % [path]
|
|
457
|
+
self.purge_deleted_applets( path )
|
|
458
|
+
elsif !file.loaded_okay?
|
|
459
|
+
self.log.warning "File %s could not be loaded: %s" %
|
|
460
|
+
[path, file.exception.message]
|
|
461
|
+
file.exception.backtrace.each do |frame|
|
|
462
|
+
self.log.debug " " + frame
|
|
463
|
+
end
|
|
464
|
+
else
|
|
465
|
+
self.log.debug "File %p has not changed." % [path]
|
|
466
|
+
return nil
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
self.log.debug "Attempting to load applet objects from %p" % path
|
|
471
|
+
@filemap[ path ] = AppletFile.new( path )
|
|
472
|
+
|
|
473
|
+
@filemap[ path ].appletclasses.each do |appletclass|
|
|
474
|
+
self.log.debug "Registering applet class %s from %p" % [appletclass.name, path]
|
|
475
|
+
begin
|
|
476
|
+
uris = self.register_applet_class( appletclass )
|
|
477
|
+
@filemap[ path ].uris << uris
|
|
478
|
+
rescue ::Exception => err
|
|
479
|
+
frames = filter_backtrace( err.backtrace )
|
|
480
|
+
self.log.error "%s loaded, but failed to initialize: %s" % [
|
|
481
|
+
appletclass.normalized_name,
|
|
482
|
+
err.message,
|
|
483
|
+
]
|
|
484
|
+
self.log.debug " " + frames.collect {|frame| "[%s]" % frame }.join(" ")
|
|
485
|
+
@filemap[ path ].exception = err
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
### Register an instance of the given +klass+ with the broker if the
|
|
493
|
+
### classmap includes it, returning the URIs which were mapped to
|
|
494
|
+
### instances of the +klass+.
|
|
495
|
+
def register_applet_class( klass )
|
|
496
|
+
uris = []
|
|
497
|
+
|
|
498
|
+
# Trim the Module serving as private namespace from the
|
|
499
|
+
# class name
|
|
500
|
+
appletname = klass.normalized_name
|
|
501
|
+
self.log.debug "Registering %p applet as %p" % [ klass.name, appletname ]
|
|
502
|
+
|
|
503
|
+
# Look for a uri corresponding to the loaded class, and instantiate it
|
|
504
|
+
# if there is one.
|
|
505
|
+
if @classmap.key?( appletname )
|
|
506
|
+
self.log.debug " Found one or more uris for '%s'" % appletname
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
# Create a new instance of the applet for each uri it's
|
|
510
|
+
# registered under, then wrap that in a RegistryEntry
|
|
511
|
+
# and put it in the entries hash we'll return later.
|
|
512
|
+
@classmap[ appletname ].each do |uri|
|
|
513
|
+
@urispace[ uri ] = klass.new( @config, @template_factory, uri )
|
|
514
|
+
uris << uri
|
|
515
|
+
end
|
|
516
|
+
else
|
|
517
|
+
self.log.debug "No uri for '%s': Not instantiated" % appletname
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
return uris
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
### Make and return a Hash which inverts the registry's applet
|
|
525
|
+
### layout into a map of class name to the URIs onto which instances of
|
|
526
|
+
### them should be installed.
|
|
527
|
+
def build_classmap
|
|
528
|
+
classmap = Hash.new {|ary,k| ary[k] = []}
|
|
529
|
+
|
|
530
|
+
# Invert the applet layout into Class => [ uris ] so as classes
|
|
531
|
+
# load, we know where to put 'em.
|
|
532
|
+
@config.applets.layout.each do |uri, klassname|
|
|
533
|
+
uri = uri.to_s.sub( %r{^/}, '' )
|
|
534
|
+
self.log.debug "Mapping %p to %p" % [ klassname, uri ]
|
|
535
|
+
classmap[ klassname ] << uri
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
return classmap
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
### Find applet files by looking in the applets path of the registry's
|
|
543
|
+
### configuration for files matching the configured pattern. Return an
|
|
544
|
+
### Array of fully-qualified applet files. If the optional +excludeList+
|
|
545
|
+
### is given, exclude any files specified from the return value.
|
|
546
|
+
def find_appletfiles( excludeList=[] )
|
|
547
|
+
files = []
|
|
548
|
+
dirCount = 0
|
|
549
|
+
|
|
550
|
+
# The Arrow::Path object will only give us extant directories...
|
|
551
|
+
@path.each do |path|
|
|
552
|
+
|
|
553
|
+
# Look for files under a directory
|
|
554
|
+
dirCount += 1
|
|
555
|
+
pat = File.join( path, @config.applets.pattern )
|
|
556
|
+
pat.untaint
|
|
557
|
+
|
|
558
|
+
self.log.debug "Looking for applets: %p" % [ pat ]
|
|
559
|
+
files.push( *Dir[ pat ] )
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
self.log.info "Fetched %d applet file paths from %d directories (out of %d)" %
|
|
563
|
+
[ files.nitems, dirCount, @path.dirs.nitems ]
|
|
564
|
+
|
|
565
|
+
files.each {|file| file.untaint }
|
|
566
|
+
return files - excludeList
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
#######
|
|
573
|
+
private
|
|
574
|
+
#######
|
|
575
|
+
|
|
576
|
+
### Return frames from the given +backtrace+ that didn't come from the
|
|
577
|
+
### current file.
|
|
578
|
+
def filter_backtrace( backtrace )
|
|
579
|
+
filtered = []
|
|
580
|
+
backtrace.each do |frame|
|
|
581
|
+
break if frame.include?(__FILE__)
|
|
582
|
+
filtered.push( frame )
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
return filtered
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
end # class Arrow::AppletRegistry
|
|
589
|
+
|
|
590
|
+
|