safety-pin 0.0.9
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/.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
|