safety-pin 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +21 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/bin/safety-pin +45 -0
- data/console_loader.rb +7 -0
- data/lib/safety-pin.rb +2 -0
- data/lib/safety_pin/jcr.rb +42 -0
- data/lib/safety_pin/node.rb +389 -0
- data/lib/safety_pin/node_blueprint.rb +18 -0
- data/lib/safety_pin/query/where_condition.rb +25 -0
- data/lib/safety_pin/query.rb +70 -0
- data/lib/safety_pin/version.rb +3 -0
- data/lib/safety_pin.rb +11 -0
- data/safety-pin.gemspec +17 -0
- data/spec/jcr_spec.rb +31 -0
- data/spec/jcr_sql2_spec.rb +81 -0
- data/spec/node_blueprint_spec.rb +35 -0
- data/spec/node_spec.rb +712 -0
- data/spec/query_spec.rb +114 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/where_condition_spec.rb +20 -0
- metadata +79 -0
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--format p --color
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create jruby-1.6.6@safety_pin
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.3)
|
5
|
+
nyan-cat-formatter (0.2.0)
|
6
|
+
rspec (2.10.0)
|
7
|
+
rspec-core (~> 2.10.0)
|
8
|
+
rspec-expectations (~> 2.10.0)
|
9
|
+
rspec-mocks (~> 2.10.0)
|
10
|
+
rspec-core (2.10.1)
|
11
|
+
rspec-expectations (2.10.0)
|
12
|
+
diff-lcs (~> 1.1.3)
|
13
|
+
rspec-mocks (2.10.1)
|
14
|
+
|
15
|
+
PLATFORMS
|
16
|
+
java
|
17
|
+
ruby
|
18
|
+
|
19
|
+
DEPENDENCIES
|
20
|
+
nyan-cat-formatter
|
21
|
+
rspec (~> 2.10.0)
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Jordan Raine
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# SafetyPin
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'safety_pin'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install safety_pin
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/safety-pin
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
options = {}
|
5
|
+
option_parser = OptionParser.new do |opts|
|
6
|
+
opts.banner = "Usage: #{File.basename(__FILE__)} [-h HOST | -d]"
|
7
|
+
|
8
|
+
opts.on("-h", "--host HOST", "JCR host (e.g., http://localhost:4502)") do |host|
|
9
|
+
options[:host] = host
|
10
|
+
end
|
11
|
+
|
12
|
+
opts.on("-d", "Developer mode -- connects to http://admin:admin@localhost:4502") do |developer_mode|
|
13
|
+
options[:developer_mode] = !!developer_mode
|
14
|
+
end
|
15
|
+
|
16
|
+
opts.on_tail("--help", "Show this message") do
|
17
|
+
puts opts
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
option_parser.parse!
|
23
|
+
|
24
|
+
unless options[:host] || options[:developer_mode]
|
25
|
+
puts option_parser
|
26
|
+
exit 1
|
27
|
+
end
|
28
|
+
|
29
|
+
if options[:developer_mode]
|
30
|
+
host = "http://localhost:4502"
|
31
|
+
username = "admin"
|
32
|
+
password = "cq4me"
|
33
|
+
else
|
34
|
+
host = options.fetch(:host)
|
35
|
+
print "Username: "
|
36
|
+
username = gets.chomp
|
37
|
+
print "Password: "
|
38
|
+
password = gets.chomp
|
39
|
+
end
|
40
|
+
|
41
|
+
ENV["HOST"] = host
|
42
|
+
ENV["USERNAME"] = username
|
43
|
+
ENV["PASSWORD"] = password
|
44
|
+
|
45
|
+
exec "irb -r './console_loader' --simple-prompt"
|
data/console_loader.rb
ADDED
data/lib/safety-pin.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module SafetyPin
|
2
|
+
class JCR
|
3
|
+
include_class('java.lang.String') {|package,name| "J#{name}" }
|
4
|
+
include_class 'javax.jcr.SimpleCredentials'
|
5
|
+
include_class 'org.apache.jackrabbit.commons.JcrUtils'
|
6
|
+
|
7
|
+
def self.login(opts = {})
|
8
|
+
repository = JcrUtils.get_repository(parse_hostname(opts[:hostname]))
|
9
|
+
creds = SimpleCredentials.new(opts[:username], JString.new(opts[:password]).to_char_array)
|
10
|
+
@@session = repository.login(creds)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.logout
|
14
|
+
session.logout
|
15
|
+
not session.live?
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.logged_in?
|
19
|
+
session.live?
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.logged_out?
|
23
|
+
not logged_in?
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.session
|
27
|
+
@@session
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.parse_hostname(hostname)
|
31
|
+
if hostname.end_with?("/crx/server")
|
32
|
+
hostname
|
33
|
+
else
|
34
|
+
hostname + "/crx/server"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.dev_login
|
39
|
+
login(:hostname => "http://localhost:4502", :username => "admin", :password => "admin")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,389 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module SafetyPin
|
4
|
+
class Node
|
5
|
+
include_class 'javax.jcr.PropertyType'
|
6
|
+
include_class 'java.util.Calendar'
|
7
|
+
include_class 'java.util.Date'
|
8
|
+
|
9
|
+
attr_reader :j_node
|
10
|
+
|
11
|
+
def self.find(path)
|
12
|
+
raise ArgumentError unless path.to_s.start_with?("/")
|
13
|
+
Node.new(session.get_node(path.to_s))
|
14
|
+
rescue javax.jcr.PathNotFoundException
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find_or_create(path, primary_type = nil)
|
19
|
+
node_blueprint = NodeBlueprint.new(:path => path.to_s, :primary_type => primary_type)
|
20
|
+
find(path) || create(node_blueprint)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.exists?(path)
|
24
|
+
find(path) != nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.session
|
28
|
+
JCR.session
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.build(node_blueprint)
|
32
|
+
raise NodeError.new("NodeBlueprint is nil") if node_blueprint.nil?
|
33
|
+
raise NodeError.new("NodeBlueprint has non-absolute path") unless node_blueprint.path.to_s.start_with?("/")
|
34
|
+
raise NodeError.new("Node already exists at path: #{node_blueprint.path}") if Node.exists?(node_blueprint.path)
|
35
|
+
|
36
|
+
rel_path_to_root_node = node_blueprint.path.to_s[1..-1]
|
37
|
+
node = self.new(session.root_node.add_node(rel_path_to_root_node, node_blueprint.primary_type))
|
38
|
+
node.properties = node_blueprint.properties
|
39
|
+
|
40
|
+
node
|
41
|
+
rescue javax.jcr.PathNotFoundException => e
|
42
|
+
raise NodeError.new("Cannot add a new node to a non-existing parent at #{node_blueprint.path}")
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.update(node_blueprint)
|
46
|
+
node = find(node_blueprint.path)
|
47
|
+
# raise NodeError.new("Cannot retrieve node for update -- might not exist") if node.nil?
|
48
|
+
node.properties = node_blueprint.properties
|
49
|
+
node.primary_type = node_blueprint.primary_type
|
50
|
+
node.save
|
51
|
+
node
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.create(node_blueprint)
|
55
|
+
node = self.build(node_blueprint)
|
56
|
+
node.save
|
57
|
+
node
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.create_parents(path)
|
61
|
+
intermediate_paths = []
|
62
|
+
|
63
|
+
current_intermediate_path = Pathname(path)
|
64
|
+
while(current_intermediate_path.to_s != "/")
|
65
|
+
current_intermediate_path = current_intermediate_path.parent
|
66
|
+
intermediate_paths.push(current_intermediate_path)
|
67
|
+
end
|
68
|
+
|
69
|
+
results = intermediate_paths.reverse.map do |intermediate_path|
|
70
|
+
create(NodeBlueprint.new(:path => intermediate_path.to_s)) unless exists?(intermediate_path)
|
71
|
+
end
|
72
|
+
|
73
|
+
session.save
|
74
|
+
|
75
|
+
results
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.create_or_update(node_blueprint_or_node_blueprints)
|
79
|
+
node_blueprints = Array(node_blueprint_or_node_blueprints)
|
80
|
+
node_blueprints.map do |node_blueprint|
|
81
|
+
if exists?(node_blueprint.path)
|
82
|
+
update(node_blueprint)
|
83
|
+
else
|
84
|
+
create(node_blueprint)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def initialize(j_node)
|
90
|
+
@j_node = j_node
|
91
|
+
end
|
92
|
+
|
93
|
+
def path
|
94
|
+
@path ||= j_node.path
|
95
|
+
end
|
96
|
+
|
97
|
+
def session
|
98
|
+
@session ||= JCR.session
|
99
|
+
end
|
100
|
+
|
101
|
+
def ==(other_node)
|
102
|
+
return false if other_node.nil?
|
103
|
+
return false unless other_node.respond_to?(:path)
|
104
|
+
self.path == other_node.path
|
105
|
+
end
|
106
|
+
|
107
|
+
def parent
|
108
|
+
raise NodeError.new("Root node does not have parent") if path == "/"
|
109
|
+
Node.new(j_node.parent)
|
110
|
+
end
|
111
|
+
|
112
|
+
def children
|
113
|
+
child_nodes = []
|
114
|
+
j_node.get_nodes.each do |child_j_node|
|
115
|
+
child_nodes << Node.new(child_j_node)
|
116
|
+
end
|
117
|
+
child_nodes
|
118
|
+
end
|
119
|
+
|
120
|
+
def child(relative_path)
|
121
|
+
child_j_node = j_node.get_node(relative_path.to_s)
|
122
|
+
Node.new(child_j_node)
|
123
|
+
rescue javax.jcr.PathNotFoundException
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
|
127
|
+
def name
|
128
|
+
@name ||= j_node.name
|
129
|
+
end
|
130
|
+
|
131
|
+
def read_attribute(name)
|
132
|
+
name = name.to_s
|
133
|
+
property = j_node.get_property(name)
|
134
|
+
if property_is_multi_valued?(property)
|
135
|
+
retrieve_property_multi_value(property)
|
136
|
+
else
|
137
|
+
retrieve_property_value(property)
|
138
|
+
end
|
139
|
+
rescue javax.jcr.PathNotFoundException
|
140
|
+
raise NilPropertyError.new("#{name} property not found on node")
|
141
|
+
end
|
142
|
+
|
143
|
+
def property_is_multi_valued?(property)
|
144
|
+
property.values
|
145
|
+
true
|
146
|
+
rescue javax.jcr.ValueFormatException
|
147
|
+
false
|
148
|
+
end
|
149
|
+
|
150
|
+
def retrieve_property_multi_value(property)
|
151
|
+
property.values.map {|value| retrieve_value(value) }
|
152
|
+
end
|
153
|
+
|
154
|
+
def retrieve_property_value(property)
|
155
|
+
retrieve_value(property.value)
|
156
|
+
end
|
157
|
+
|
158
|
+
def retrieve_value(value)
|
159
|
+
property_type = PropertyType.name_from_value(value.type)
|
160
|
+
case property_type
|
161
|
+
when "String"
|
162
|
+
value.string
|
163
|
+
when "Boolean"
|
164
|
+
value.boolean
|
165
|
+
when "Double"
|
166
|
+
value.double
|
167
|
+
when "Long"
|
168
|
+
value.long
|
169
|
+
when "Date"
|
170
|
+
Time.at(value.date.time.time / 1000)
|
171
|
+
when "Name"
|
172
|
+
value.string # Not sure if these should be handled differently
|
173
|
+
else
|
174
|
+
raise PropertyTypeError.new("Unknown property type: #{property_type}")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def write_attribute(name, value)
|
179
|
+
raise PropertyError.new("Illegal operation: cannot change jcr:primaryType property") if name == "jcr:primaryType"
|
180
|
+
name = name.to_s
|
181
|
+
|
182
|
+
if value.nil? and not j_node.has_property(name)
|
183
|
+
return nil
|
184
|
+
end
|
185
|
+
|
186
|
+
if value.is_a? Array
|
187
|
+
values = value
|
188
|
+
val_fact = value_factory
|
189
|
+
j_values = []
|
190
|
+
values.each do |value|
|
191
|
+
j_values << val_fact.create_value(value.to_java)
|
192
|
+
end
|
193
|
+
j_node.set_property(name, j_values.to_java(Java::JavaxJcr::Value))
|
194
|
+
elsif value.is_a? Time or value.is_a? Date
|
195
|
+
calendar_value = Calendar.instance
|
196
|
+
calendar_value.set_time(value.to_java)
|
197
|
+
j_node.set_property(name, calendar_value)
|
198
|
+
elsif value.is_a? Symbol
|
199
|
+
j_node.set_property(name, value.to_s)
|
200
|
+
else
|
201
|
+
begin
|
202
|
+
j_node.set_property(name, value)
|
203
|
+
rescue NameError
|
204
|
+
raise SafetyPin::PropertyTypeError.new("Property value type of #{value.class} is unsupported")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def save
|
210
|
+
if new?
|
211
|
+
j_node.parent.save
|
212
|
+
else
|
213
|
+
j_node.save
|
214
|
+
end
|
215
|
+
|
216
|
+
not changed?
|
217
|
+
end
|
218
|
+
|
219
|
+
def reload
|
220
|
+
j_node.refresh(false)
|
221
|
+
end
|
222
|
+
|
223
|
+
def [](name)
|
224
|
+
read_attribute(name)
|
225
|
+
end
|
226
|
+
|
227
|
+
def []=(name, value)
|
228
|
+
write_attribute(name, value)
|
229
|
+
end
|
230
|
+
|
231
|
+
def changed?
|
232
|
+
j_node.modified?
|
233
|
+
end
|
234
|
+
|
235
|
+
def new?
|
236
|
+
j_node.new?
|
237
|
+
end
|
238
|
+
|
239
|
+
def properties
|
240
|
+
props = {}
|
241
|
+
prop_iter = j_node.properties
|
242
|
+
while prop_iter.has_next
|
243
|
+
prop = prop_iter.next_property
|
244
|
+
unless prop.definition.protected?
|
245
|
+
prop_name = prop.name
|
246
|
+
props[prop_name] = self[prop_name]
|
247
|
+
end
|
248
|
+
end
|
249
|
+
props
|
250
|
+
end
|
251
|
+
|
252
|
+
def protected_properties
|
253
|
+
props = {}
|
254
|
+
prop_iter = j_node.properties
|
255
|
+
while prop_iter.has_next
|
256
|
+
prop = prop_iter.next_property
|
257
|
+
if prop.definition.protected?
|
258
|
+
prop_name = prop.name
|
259
|
+
props[prop_name] = self[prop_name]
|
260
|
+
end
|
261
|
+
end
|
262
|
+
props
|
263
|
+
end
|
264
|
+
|
265
|
+
def properties=(new_props)
|
266
|
+
property_names = (properties.keys + new_props.keys).uniq
|
267
|
+
property_names.each do |name|
|
268
|
+
# REFACTOR ME PLZ
|
269
|
+
child_path = Pathname(path.to_s) + name.to_s
|
270
|
+
if new_props[name].is_a? Hash
|
271
|
+
new_props[name] = convert_hash_to_node_blueprint(new_props[name])
|
272
|
+
end
|
273
|
+
|
274
|
+
if new_props[name].respond_to?(:node_blueprint?) and new_props[name].node_blueprint?
|
275
|
+
# Handle node blue prints
|
276
|
+
node_blueprint = NodeBlueprint.new(:properties => new_props[name].properties,
|
277
|
+
:path => child_path.to_s,
|
278
|
+
:primary_type => new_props[name].primary_type)
|
279
|
+
if Node.exists?(child_path)
|
280
|
+
Node.update(node_blueprint)
|
281
|
+
else
|
282
|
+
Node.build(node_blueprint)
|
283
|
+
end
|
284
|
+
else
|
285
|
+
# handle everything else
|
286
|
+
self[name] = new_props[name]
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# Convert a hash (and it's values recursively) to NodeBlueprints. This is a
|
292
|
+
# helper method, allowing a hash to be passed in to Node#properties= when
|
293
|
+
# only properties need to be set. One caveat: all node types will default
|
294
|
+
# to nt:unstructured.
|
295
|
+
def convert_hash_to_node_blueprint(hash)
|
296
|
+
hash.keys.each do |key|
|
297
|
+
if hash[key].is_a? Hash
|
298
|
+
hash[key] = convert_hash_to_node_blueprint(hash[key])
|
299
|
+
end
|
300
|
+
end
|
301
|
+
NodeBlueprint.new(:path => :no_path, :properties => hash)
|
302
|
+
end
|
303
|
+
|
304
|
+
def value_factory
|
305
|
+
session.value_factory
|
306
|
+
end
|
307
|
+
|
308
|
+
def destroy
|
309
|
+
path = self.path
|
310
|
+
parent_j_node = j_node.parent
|
311
|
+
j_node.remove
|
312
|
+
parent_j_node.save
|
313
|
+
# raise NodeError.new("Unable to destroy #{path} node") unless self.class.find(path).nil?
|
314
|
+
rescue javax.jcr.RepositoryException => e
|
315
|
+
raise NodeError.new("Unable to destroy #{path} node: #{e.message}")
|
316
|
+
end
|
317
|
+
|
318
|
+
def mixin_types
|
319
|
+
j_node.mixin_node_types.map(&:name)
|
320
|
+
end
|
321
|
+
|
322
|
+
def add_mixin(mixin_name)
|
323
|
+
j_node.add_mixin(mixin_name)
|
324
|
+
end
|
325
|
+
|
326
|
+
def remove_mixin(mixin_name)
|
327
|
+
j_node.remove_mixin(mixin_name)
|
328
|
+
end
|
329
|
+
|
330
|
+
def primary_type
|
331
|
+
self["jcr:primaryType"]
|
332
|
+
end
|
333
|
+
|
334
|
+
def primary_type=(primary_type)
|
335
|
+
j_node.set_primary_type(primary_type)
|
336
|
+
end
|
337
|
+
|
338
|
+
def find_or_create(name, primary_type = nil)
|
339
|
+
path = Pathname(self.path) + name
|
340
|
+
self.class.find_or_create(path.to_s, primary_type)
|
341
|
+
end
|
342
|
+
|
343
|
+
# Create and return a child node with a given name
|
344
|
+
def create(name, node_blueprint = nil)
|
345
|
+
Node.create(node_blueprint_for(name, node_blueprint))
|
346
|
+
end
|
347
|
+
|
348
|
+
def build(name, node_blueprint = nil)
|
349
|
+
Node.build(node_blueprint_for(name, node_blueprint))
|
350
|
+
end
|
351
|
+
|
352
|
+
def node_blueprint_for(name, node_blueprint = nil)
|
353
|
+
path = Pathname(self.path) + name.to_s
|
354
|
+
|
355
|
+
unless node_blueprint.nil?
|
356
|
+
properties = node_blueprint.properties
|
357
|
+
primary_type = node_blueprint.primary_type
|
358
|
+
end
|
359
|
+
|
360
|
+
NodeBlueprint.new(:path => path.to_s, :properties => properties, :primary_type => primary_type)
|
361
|
+
end
|
362
|
+
|
363
|
+
def replace_property(opts)
|
364
|
+
opts = {recursive: false}.merge(opts)
|
365
|
+
name = opts.fetch(:name)
|
366
|
+
target = opts.fetch(:target)
|
367
|
+
replacement = opts.fetch(:replacement)
|
368
|
+
|
369
|
+
modified_nodes = []
|
370
|
+
if has_property(name) and self[name].match(target)
|
371
|
+
self[name] = replacement
|
372
|
+
modified_nodes << self
|
373
|
+
end
|
374
|
+
|
375
|
+
modified_nodes << children.map {|child_node| child_node.replace_property(opts) } if opts.fetch(:recursive)
|
376
|
+
|
377
|
+
modified_nodes.flatten
|
378
|
+
end
|
379
|
+
|
380
|
+
def has_property(name)
|
381
|
+
properties.keys.include?(name)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
class NodeError < Exception; end
|
386
|
+
class PropertyTypeError < Exception; end
|
387
|
+
class NilPropertyError < Exception; end
|
388
|
+
class PropertyError < Exception; end
|
389
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SafetyPin
|
2
|
+
class NodeBlueprint
|
3
|
+
attr_accessor :path, :primary_type, :properties
|
4
|
+
|
5
|
+
def initialize(opts)
|
6
|
+
raise NodeBlueprintError.new("No path specified") unless opts.has_key?(:path)
|
7
|
+
@path = opts[:path]
|
8
|
+
@primary_type = opts[:primary_type] || "nt:unstructured"
|
9
|
+
@properties = opts[:properties] || {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def node_blueprint?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class NodeBlueprintError < Exception; end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SafetyPin
|
2
|
+
class Query
|
3
|
+
class WhereCondition
|
4
|
+
attr_reader :name, :value, :comparator
|
5
|
+
|
6
|
+
def initialize(name, value, comparator = "=")
|
7
|
+
@name = name
|
8
|
+
@value = value
|
9
|
+
@comparator = comparator
|
10
|
+
end
|
11
|
+
|
12
|
+
def sql_fragment
|
13
|
+
"[#{name}] #{comparator} '#{value}'"
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(object)
|
17
|
+
self.name == object.name and self.value == object.value and self.comparator == object.comparator
|
18
|
+
end
|
19
|
+
|
20
|
+
def eql?(object)
|
21
|
+
self == object
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module SafetyPin
|
2
|
+
class Query
|
3
|
+
def self.execute(query_string)
|
4
|
+
query = query_manager.create_query(query_string, "JCR-SQL2")
|
5
|
+
node_iter = query.execute.nodes
|
6
|
+
nodes = []
|
7
|
+
while node_iter.has_next
|
8
|
+
nodes << Node.new(node_iter.next_node)
|
9
|
+
end
|
10
|
+
nodes
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.query_manager
|
14
|
+
JCR.session.workspace.query_manager
|
15
|
+
end
|
16
|
+
|
17
|
+
def sql
|
18
|
+
[select_statement, where_statement].compact.join(" ")
|
19
|
+
end
|
20
|
+
|
21
|
+
def type(type)
|
22
|
+
@type = type
|
23
|
+
end
|
24
|
+
|
25
|
+
def where(properties)
|
26
|
+
self.where_conditions = where_conditions + properties.map {|name, value| WhereCondition.new(name, value) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def within(path)
|
30
|
+
@within ||= []
|
31
|
+
if path.is_a? String
|
32
|
+
@within << path
|
33
|
+
elsif path.is_a? Array
|
34
|
+
@within += path
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def where_conditions
|
39
|
+
@where_conditions ||= []
|
40
|
+
end
|
41
|
+
|
42
|
+
def where_conditions=(where_conditions)
|
43
|
+
@where_conditions = where_conditions
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def select_statement
|
48
|
+
type = @type || "nt:base"
|
49
|
+
"SELECT * FROM [#{type}]"
|
50
|
+
end
|
51
|
+
|
52
|
+
def where_statement
|
53
|
+
"WHERE #{where_sql.join(" AND ")}" unless where_conditions.empty? and within_path_conditions.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
def where_sql
|
57
|
+
(where_conditions + within_path_conditions).map(&:sql_fragment)
|
58
|
+
end
|
59
|
+
|
60
|
+
def within_path_conditions
|
61
|
+
unless @within.nil?
|
62
|
+
@within.map {|path| WhereCondition.new("jcr:path", "#{path}%", "LIKE") }
|
63
|
+
else
|
64
|
+
[]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class InvalidQuery < Exception; end
|
70
|
+
end
|