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 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
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rspec", "~> 2.10.0"
4
+ gem "nyan-cat-formatter"
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
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
@@ -0,0 +1,7 @@
1
+ $:<<File.dirname(__FILE__)
2
+ require 'lib/safety_pin'
3
+
4
+ include SafetyPin
5
+
6
+ puts "Connecting to #{ENV["HOST"]}"
7
+ JCR.login(hostname: ENV["HOST"], username: ENV["USERNAME"], password: ENV["PASSWORD"])
data/lib/safety-pin.rb ADDED
@@ -0,0 +1,2 @@
1
+ # Make sure `require 'safety-pin'` works too
2
+ require File.dirname(__FILE__) + '/safety_pin'
@@ -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
@@ -0,0 +1,3 @@
1
+ module SafetyPin
2
+ VERSION = "0.0.9"
3
+ end