safety-pin 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
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