safety_pin 0.0.1

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 progress --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create jruby-1.6.7@safety_pin
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rspec", "~> 2.10.0"
data/Gemfile.lock ADDED
@@ -0,0 +1,18 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ rspec (2.10.0)
6
+ rspec-core (~> 2.10.0)
7
+ rspec-expectations (~> 2.10.0)
8
+ rspec-mocks (~> 2.10.0)
9
+ rspec-core (2.10.0)
10
+ rspec-expectations (2.10.0)
11
+ diff-lcs (~> 1.1.3)
12
+ rspec-mocks (2.10.1)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ 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"
@@ -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,242 @@
1
+ module SafetyPin
2
+ class Node
3
+ include_class 'javax.jcr.PropertyType'
4
+ include_class 'java.util.Calendar'
5
+ include_class 'java.util.Date'
6
+
7
+ attr_reader :j_node
8
+
9
+ def self.find(path)
10
+ raise ArgumentError unless path.to_s.start_with?("/")
11
+ Node.new(session.get_node(path.to_s))
12
+ rescue javax.jcr.PathNotFoundException
13
+ nil
14
+ end
15
+
16
+ def self.session
17
+ JCR.session
18
+ end
19
+
20
+ def self.build(path, node_type = nil)
21
+ node_type ||= "nt:unstructured"
22
+ rel_path = nil
23
+ if path.start_with?("/")
24
+ rel_path = path.sub("/","")
25
+ else
26
+ raise ArgumentError.new("Given path not absolute: #{path}")
27
+ end
28
+
29
+ if session.root_node.has_node(rel_path)
30
+ raise NodeError.new("Node already exists at path: #{path}")
31
+ else
32
+ self.new(session.root_node.add_node(rel_path, node_type))
33
+ end
34
+ rescue javax.jcr.PathNotFoundException => e
35
+ raise NodeError.new("Cannot add a new node to a non-existing parent at #{path}")
36
+ end
37
+
38
+ def self.create(path, node_type = nil)
39
+ node = self.build(path, node_type)
40
+ node.save
41
+ node
42
+ end
43
+
44
+ def initialize(j_node)
45
+ @j_node = j_node
46
+ end
47
+
48
+ def path
49
+ @path ||= j_node.path
50
+ end
51
+
52
+ def session
53
+ @session ||= JCR.session
54
+ end
55
+
56
+ def children
57
+ child_nodes = []
58
+ j_node.get_nodes.each do |child_j_node|
59
+ child_nodes << Node.new(child_j_node)
60
+ end
61
+ child_nodes
62
+ end
63
+
64
+ def child(relative_path)
65
+ child_j_node = j_node.get_node(relative_path)
66
+ Node.new(child_j_node)
67
+ rescue javax.jcr.PathNotFoundException
68
+ nil
69
+ end
70
+
71
+ def name
72
+ @name ||= j_node.name
73
+ end
74
+
75
+ def read_attribute(name)
76
+ property = j_node.get_property(name)
77
+ if property_is_multi_valued?(property)
78
+ retrieve_property_multi_value(property)
79
+ else
80
+ retrieve_property_value(property)
81
+ end
82
+ rescue javax.jcr.PathNotFoundException
83
+ raise NilPropertyError.new("#{name} property not found on node")
84
+ end
85
+
86
+ def property_is_multi_valued?(property)
87
+ property.values
88
+ true
89
+ rescue javax.jcr.ValueFormatException
90
+ false
91
+ end
92
+
93
+ def retrieve_property_multi_value(property)
94
+ property.values.map {|value| retrieve_value(value) }
95
+ end
96
+
97
+ def retrieve_property_value(property)
98
+ retrieve_value(property.value)
99
+ end
100
+
101
+ def retrieve_value(value)
102
+ property_type = PropertyType.name_from_value(value.type)
103
+ case property_type
104
+ when "String"
105
+ value.string
106
+ when "Boolean"
107
+ value.boolean
108
+ when "Double"
109
+ value.double
110
+ when "Long"
111
+ value.long
112
+ when "Date"
113
+ Time.at(value.date.time.time / 1000)
114
+ when "Name"
115
+ value.string # Not sure if these should be handled differently
116
+ else
117
+ raise PropertyTypeError.new("Unknown property type: #{property_type}")
118
+ end
119
+ end
120
+
121
+ def write_attribute(name, value)
122
+ raise PropertyError.new("Illegal operation: cannot change jcr:primaryType property") if name == "jcr:primaryType"
123
+
124
+ if value.is_a? Array
125
+ values = value
126
+ val_fact = value_factory
127
+ j_values = []
128
+ values.each do |value|
129
+ j_values << val_fact.create_value(value.to_java)
130
+ end
131
+ j_node.set_property(name, j_values.to_java(Java::JavaxJcr::Value))
132
+ elsif value.is_a? Time or value.is_a? Date
133
+ calendar_value = Calendar.instance
134
+ calendar_value.set_time(value.to_java)
135
+ j_node.set_property(name, calendar_value)
136
+ else
137
+ j_node.set_property(name, value)
138
+ end
139
+ end
140
+
141
+ def save
142
+ if new?
143
+ j_node.parent.save
144
+ else
145
+ j_node.save
146
+ end
147
+
148
+ not changed?
149
+ end
150
+
151
+ def reload
152
+ j_node.refresh(false)
153
+ end
154
+
155
+ def [](name)
156
+ read_attribute(name)
157
+ end
158
+
159
+ def []=(name, value)
160
+ write_attribute(name, value)
161
+ end
162
+
163
+ def changed?
164
+ j_node.modified?
165
+ end
166
+
167
+ def new?
168
+ j_node.new?
169
+ end
170
+
171
+ def properties
172
+ props = {}
173
+ prop_iter = j_node.properties
174
+ while prop_iter.has_next
175
+ prop = prop_iter.next_property
176
+ unless prop.definition.protected?
177
+ prop_name = prop.name
178
+ props[prop_name] = self[prop_name]
179
+ end
180
+ end
181
+ props
182
+ end
183
+
184
+ def protected_properties
185
+ props = {}
186
+ prop_iter = j_node.properties
187
+ while prop_iter.has_next
188
+ prop = prop_iter.next_property
189
+ if prop.definition.protected?
190
+ prop_name = prop.name
191
+ props[prop_name] = self[prop_name]
192
+ end
193
+ end
194
+ props
195
+ end
196
+
197
+ def properties=(new_props)
198
+ # props.each do |name, value|
199
+ # self[name] = value
200
+ # end
201
+ property_names = (properties.keys + new_props.keys).uniq
202
+ property_names.each do |name|
203
+ self[name] = new_props[name]
204
+ end
205
+ end
206
+
207
+ def value_factory
208
+ session.value_factory
209
+ end
210
+
211
+ def destroy
212
+ path = self.path
213
+ parent_j_node = j_node.parent
214
+ j_node.remove
215
+ parent_j_node.save
216
+ # raise NodeError.new("Unable to destroy #{path} node") unless self.class.find(path).nil?
217
+ rescue javax.jcr.RepositoryException => e
218
+ raise NodeError.new("Unable to destroy #{path} node: #{e.message}")
219
+ end
220
+
221
+ def mixin_types
222
+ j_node.mixin_node_types.map(&:name)
223
+ end
224
+
225
+ def add_mixin(mixin_name)
226
+ j_node.add_mixin(mixin_name)
227
+ end
228
+
229
+ def remove_mixin(mixin_name)
230
+ j_node.remove_mixin(mixin_name)
231
+ end
232
+
233
+ def primary_type
234
+ self["jcr:primaryType"]
235
+ end
236
+ end
237
+
238
+ class NodeError < Exception; end
239
+ class PropertyTypeError < Exception; end
240
+ class NilPropertyError < Exception; end
241
+ class PropertyError < Exception; end
242
+ 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.1"
3
+ end
data/lib/safety_pin.rb ADDED
@@ -0,0 +1,10 @@
1
+ $: << File.dirname(__FILE__)
2
+
3
+ require 'java'
4
+ require 'safety_pin/jcr'
5
+ require 'safety_pin/node'
6
+ require 'safety_pin/query'
7
+ require 'safety_pin/query/where_condition'
8
+
9
+ module SafetyPin
10
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/safety_pin/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Jordan Raine"]
6
+ gem.email = ["jnraine@gmail.com"]
7
+ gem.description = %q{An easy-to-use JCR connector for JRuby}
8
+ gem.summary = %q{JCR connector for JRuby}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "safety_pin"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = SafetyPin::VERSION
17
+ end
data/spec/jcr_spec.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe SafetyPin::JCR do
4
+ it "should login to a remote SafetyPin::JCR" do
5
+ SafetyPin::JCR.login(:hostname => "http://localhost:4502", :username => "admin", :password => "admin")
6
+ SafetyPin::JCR.session.should be_a(Java::JavaxJcr::Session)
7
+ SafetyPin::JCR.should be_logged_in
8
+ SafetyPin::JCR.logout
9
+ end
10
+
11
+ it "should logout of a remote SafetyPin::JCR" do
12
+ SafetyPin::JCR.login(:hostname => "http://localhost:4502", :username => "admin", :password => "admin")
13
+ SafetyPin::JCR.logout
14
+ SafetyPin::JCR.should be_logged_out
15
+ end
16
+
17
+ context ".parse_hostname" do
18
+ it "ensures the hostname ends with /crx/server" do
19
+ hostname = SafetyPin::JCR.parse_hostname("http://localhost:4502")
20
+ hostname.end_with?("/crx/server").should be_true
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe "JCR-SQL2 example queries" do
4
+ before(:all) do
5
+ SafetyPin::JCR.login(:hostname => "http://localhost:4502", :username => "admin", :password => "admin")
6
+ end
7
+
8
+ before do
9
+ SafetyPin::JCR.session.refresh(false)
10
+ end
11
+
12
+ after(:all) do
13
+ SafetyPin::JCR.logout
14
+ end
15
+
16
+ before do
17
+ @node = SafetyPin::Node.create("/content/foo")
18
+ @node.properties = {"bar" => "baz", "qux" => "qax"}
19
+ @node.save
20
+ end
21
+
22
+ after { @node.destroy }
23
+
24
+ it "can lookup a node based on property presence" do
25
+ nodes = SafetyPin::Query.execute("SELECT * FROM [nt:base] WHERE [nt:base].bar IS NOT NULL")
26
+ nodes.first["bar"].should_not be_nil
27
+ end
28
+
29
+ it "can lookup a node based on a property's value" do
30
+ nodes = SafetyPin::Query.execute("SELECT * FROM [nt:base] WHERE bar = 'baz'")
31
+ values = nodes.map {|e| e["bar"] }.uniq
32
+ values.length.should eql(1)
33
+ values.first.should eql("baz")
34
+ end
35
+
36
+ it "can lookup a node based on multiple property values" do
37
+ nodes = SafetyPin::Query.execute("SELECT * FROM [nt:base] WHERE bar = 'baz' AND qux = 'qax'")
38
+ bar_values = nodes.map {|e| e["bar"] }.uniq
39
+ qux_values = nodes.map {|e| e["qux"] }.uniq
40
+ bar_values.length.should eql(1)
41
+ qux_values.length.should eql(1)
42
+ bar_values.first.should eql("baz")
43
+ qux_values.first.should eql("qax")
44
+ end
45
+
46
+ it "can lookup a node based on node name" do
47
+ nodes = SafetyPin::Query.execute("SELECT * FROM [nt:base] WHERE NAME([nt:base]) = 'foo'")
48
+ names = nodes.map(&:name).uniq
49
+ names.length.should eql(1)
50
+ names.first.should eql("foo")
51
+ end
52
+
53
+ it "can lookup a node based on a path" do
54
+ pending "can't do this yet"
55
+ nodes = SafetyPin::Query.execute("SELECT * FROM [nt:base] AS base WHERE base.[jcr:path] LIKE '/content/%'")
56
+ nodes.length.should be > 0
57
+ nodes.map(&:path).each {|name| name.starts_with?("/content").should be_true }
58
+ end
59
+
60
+ it "can lookup a node based on node type" do
61
+ nodes = SafetyPin::Query.execute("SELECT * FROM [cq:Page]")
62
+ nodes.first.primary_type.should eql("cq:Page")
63
+ end
64
+
65
+ it "can lookup a node based on its node super type" do
66
+ nodes = SafetyPin::Query.execute("SELECT * FROM [rep:Authorizable]")
67
+ primary_types = nodes.map(&:primary_type)
68
+ primary_types.should include("rep:User")
69
+ end
70
+
71
+ # context "given some nodes and child nodes" do
72
+ # before do
73
+ # @parent2 = SafetyPin::Node.create("/content/foo2")
74
+ # @child1 = SafetyPin::Node.create("/content/foo2/child")
75
+ # @child1.properties = {"bar" => "baz", "qux" => "qax", "child" => "yes"}
76
+ # @child1.save
77
+ # @child2 = SafetyPin::Node.create("/content/foo/child")
78
+ # @child2.properties = {"bar" => "baz", "qux" => "qax", "child" => "yes"}
79
+ # @child2.save
80
+ # end
81
+ #
82
+ # after do
83
+ # @parent2.destroy
84
+ # end
85
+ #
86
+ # it "can lookup a node based on nested WHERE conditions" do
87
+ # sql_statement = "SELECT * FROM [nt:base]
88
+ # WHERE [jcr:path] LIKE '/content/foo%'"
89
+ # nodes = SafetyPin::Query.execute(sql_statement)
90
+ # nodes.map(&:path).sort.should eql(["/content/foo/child", "/content/foo2/child"])
91
+ # end
92
+ # end
93
+ end
data/spec/node_spec.rb ADDED
@@ -0,0 +1,494 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe SafetyPin::Node do
4
+ before(:all) do
5
+ SafetyPin::JCR.login(:hostname => "http://localhost:4502", :username => "admin", :password => "admin")
6
+ end
7
+
8
+ before do
9
+ SafetyPin::JCR.session.refresh(false)
10
+ end
11
+
12
+ after(:all) do
13
+ SafetyPin::JCR.logout
14
+ end
15
+
16
+ context ".find" do
17
+ context "given a node name" do
18
+ context "that exists" do
19
+ it "should return a node with a matching path" do
20
+ SafetyPin::Node.find("/content").path.should eql("/content")
21
+ end
22
+ end
23
+
24
+ context "that doesn't exist" do
25
+ it "should return nil" do
26
+ SafetyPin::Node.find("/foo/bar/baz").should be_nil
27
+ end
28
+ end
29
+ end
30
+
31
+ it "complains if the path isn't an absolute path" do
32
+ lambda { node = SafetyPin::Node.find("content") }.should raise_exception(ArgumentError)
33
+ end
34
+ end
35
+
36
+ context ".session" do
37
+ it "should return a session" do
38
+ SafetyPin::Node.session.should be_a(Java::JavaxJcr::Session)
39
+ end
40
+ end
41
+
42
+ context "#session" do
43
+ it "should return a session" do
44
+ SafetyPin::Node.find("/content").session.should be_a(Java::JavaxJcr::Session)
45
+ end
46
+
47
+ it "should cache session in an instance variable" do
48
+ node = SafetyPin::Node.find("/content")
49
+ node.session
50
+ node.instance_eval { @session }.should be_a(Java::JavaxJcr::Session)
51
+ end
52
+ end
53
+
54
+ context "#children" do
55
+ it "should return an array of child nodes" do
56
+ SafetyPin::Node.find("/content").children.first.should be_a(SafetyPin::Node)
57
+ end
58
+ end
59
+
60
+ context "#child" do
61
+ context "given a node name" do
62
+ let(:node) { SafetyPin::Node.find("/") }
63
+
64
+ context "that exists" do
65
+ it "should return a child node with a matching name" do
66
+ node.child("content").name.should eql("content")
67
+ end
68
+
69
+ it "should return a grandchild node given a relative path" do
70
+ SafetyPin::Node.create("/content/foo")
71
+ node.child("content/foo").name.should eql("foo")
72
+ SafetyPin::Node.find("/content/foo").destroy
73
+ end
74
+ end
75
+
76
+ context "that doesn't exist" do
77
+ it "should return nil" do
78
+ node.child("foobarbaz").should be_nil
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ context "#name" do
85
+ it "should return a string name" do
86
+ SafetyPin::Node.find("/content").name.should eql("content")
87
+ end
88
+ end
89
+
90
+ describe "#save" do
91
+ context "on an existing node with changes" do
92
+ before do
93
+ @node = SafetyPin::Node.create("/content/foo")
94
+ @node["bar"] = "baz"
95
+ end
96
+
97
+ after { @node.destroy }
98
+
99
+ it "should save the changes to the JCR" do
100
+ @node.save
101
+ @node.reload
102
+ @node["bar"].should eql("baz")
103
+ end
104
+
105
+ it "should return true if the save was successful" do
106
+ save_successful = @node.save
107
+ (save_successful and not @node.changed?).should be_true
108
+ end
109
+ end
110
+
111
+ context "on a new node" do
112
+ it "should save the node" do
113
+ node = SafetyPin::Node.build("/content/foo")
114
+ node.save.should be_true
115
+ node.destroy
116
+ end
117
+
118
+ it "should save changes in parent node" do
119
+ parent_node = SafetyPin::Node.create("/content/foo")
120
+ node = SafetyPin::Node.build("/content/foo/bar")
121
+ parent_node["baz"] = "qux"
122
+ parent_node.should be_changed
123
+ node.save
124
+ parent_node.should_not be_changed
125
+ node.destroy
126
+ parent_node.destroy
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "#read_attribute" do
132
+ context "on an existing node" do
133
+ before { @node = SafetyPin::Node.create("/content/foo") }
134
+ after { @node.destroy }
135
+
136
+ it "should return the string value of a string property" do
137
+ @node["foo"] = "bar"
138
+ @node.read_attribute("foo").should eql("bar")
139
+ end
140
+
141
+ it "should return the boolean value of a boolean property" do
142
+ @node["foo"] = true
143
+ @node.read_attribute("foo").should eql(true)
144
+ end
145
+
146
+ it "should return the double value of a double (or Ruby float) property" do
147
+ @node["foo"] = 3.14
148
+ @node.read_attribute("foo").should eql(3.14)
149
+ end
150
+
151
+ it "should return the long value of a long (or Ruby Fixnum) property" do
152
+ @node["foo"] = 42
153
+ @node.read_attribute("foo").should eql(42)
154
+ end
155
+
156
+ it "should return the time value of a date property" do
157
+ time = Time.now
158
+ @node["foo"] = time
159
+ @node.read_attribute("foo").to_s.should eql(time.to_s)
160
+ end
161
+
162
+ context "given a multi-value property" do
163
+ it "should return an array of values" do
164
+ @node["foo"] = ["one", "two"]
165
+ @node.read_attribute("foo").should eql(["one", "two"])
166
+ end
167
+ end
168
+ end
169
+
170
+ it "should return the string value of a name property" do
171
+ SafetyPin::Node.find("/")["jcr:primaryType"].should eql("rep:root")
172
+ end
173
+
174
+ it "should throw an exception when accessing a non-existent (nil) property" do
175
+ lambda { SafetyPin::Node.build("/content/foo").read_attribute("foo-bar-baz") }.should raise_error(SafetyPin::NilPropertyError)
176
+ end
177
+ end
178
+
179
+ context "#write_attribute" do
180
+ before { @node = SafetyPin::Node.create("/content/foo") }
181
+ after { @node.destroy }
182
+
183
+ context "given a single value" do
184
+ it "should set a string property value" do
185
+ @node.write_attribute("foo", "bar")
186
+ @node.save
187
+ @node.reload
188
+ @node["foo"].should eql("bar")
189
+ end
190
+
191
+ context "given a Time object" do
192
+ it "should set a date property value" do
193
+ time = Time.now
194
+ @node.write_attribute("foo", time)
195
+ @node.save
196
+ @node.reload
197
+ @node["foo"].to_s.should eql(time.to_s)
198
+ end
199
+ end
200
+ end
201
+
202
+ context "given an array of values" do
203
+ context "of the same type" do
204
+ it "should set a multi-value string array" do
205
+ @node.write_attribute("foo", ["one", "two"])
206
+ @node.save
207
+ @node.reload
208
+ @node["foo"].should eql(["one", "two"])
209
+ end
210
+ end
211
+ end
212
+
213
+ context "given a null value" do
214
+ it "should remove the property" do
215
+ @node["foo"] = "bar"
216
+ @node.write_attribute("foo", nil)
217
+ lambda { @node["foo"] }.should raise_error(SafetyPin::NilPropertyError)
218
+ end
219
+ end
220
+
221
+ context "changing jcr:primaryType property" do
222
+ it "should raise an error" do
223
+ lambda { @node.write_attribute("jcr:primaryType", "nt:folder") }.should raise_error(SafetyPin::PropertyError)
224
+ end
225
+ end
226
+ end
227
+
228
+ context "#reload" do
229
+ before { @node = SafetyPin::Node.create("/content/foo") }
230
+ after { @node.destroy }
231
+
232
+ it "should discard pending changes" do
233
+ @node["foo"] = "bar"
234
+ @node.reload
235
+ lambda { @node.read_attribute("foo") }.should raise_error(SafetyPin::NilPropertyError)
236
+ end
237
+
238
+ it "should not discard changes for another node" do
239
+ @node["bar"] = "baz"
240
+ another_node = SafetyPin::Node.find("/content")
241
+ another_node["bar"] = "baz"
242
+ @node.reload
243
+ lambda { @node["bar"] }.should raise_error(SafetyPin::NilPropertyError)
244
+ another_node["bar"].should eql("baz")
245
+ end
246
+ end
247
+
248
+ describe "#[]" do
249
+ it "should return the value of a given property name" do
250
+ node = SafetyPin::Node.create("/content/foo")
251
+ node.write_attribute("bar","baz")
252
+ node.save
253
+ node["bar"].should eql("baz")
254
+ node.destroy
255
+ end
256
+ end
257
+
258
+ describe "#[]=" do
259
+ it "should set the value of a given property name" do
260
+ node = SafetyPin::Node.create("/content/foo")
261
+ node.write_attribute("bar","baz")
262
+ node["bar"] = "qux"
263
+ node["bar"].should eql("qux")
264
+ node.destroy
265
+ end
266
+ end
267
+
268
+ context "#changed?" do
269
+ let(:node) { SafetyPin::Node.find("/content") }
270
+
271
+ it "should return false if the node does not have unsaved changes" do
272
+ node.should_not be_changed
273
+ end
274
+
275
+ it "should return true if the node has unsaved changes" do
276
+ node["foo"] = "bar"
277
+ node.should be_changed
278
+ end
279
+ end
280
+
281
+ context "#new?" do
282
+ it "should return true if node has never been saved to JCR" do
283
+ SafetyPin::Node.build("/content/foo").should be_new
284
+ end
285
+
286
+ it "should return false if node has been saved to JCR" do
287
+ SafetyPin::Node.find("/content").should_not be_new
288
+ end
289
+ end
290
+
291
+ describe "#properties" do
292
+ it "should return hash of all unprotected properties" do
293
+ SafetyPin::Node.find("/").properties.should eql({"sling:target"=>"/index.html", "sling:resourceType"=>"sling:redirect"})
294
+ end
295
+ end
296
+
297
+ describe "#properties=" do
298
+ before { @node = SafetyPin::Node.create("/content/foo") }
299
+ after { @node.destroy }
300
+
301
+ it "should set the properties of a node" do
302
+ @node.properties = {"foo" => "bar"}
303
+ @node.properties.should eql({"foo" => "bar"})
304
+ end
305
+
306
+ it "should set unset properties not specified in hash" do
307
+ @node["foo"] = "bar"
308
+ @node.properties = {"baz" => "qux"}
309
+ @node.properties.should eql({"baz" => "qux"})
310
+ end
311
+ end
312
+
313
+ describe "#protected_properties" do
314
+ it "should return hash of all protected properties" do
315
+ SafetyPin::Node.find("/").protected_properties.should eql({"jcr:primaryType"=>"rep:root", "jcr:mixinTypes"=>["rep:AccessControllable", "rep:RepoAccessControllable"]})
316
+ end
317
+ end
318
+
319
+ describe "#mixin_types" do
320
+ before do
321
+ @node = SafetyPin::Node.create("/content/foo")
322
+ @node.j_node.add_mixin("mix:created")
323
+ @node.save
324
+ end
325
+
326
+ after { @node.destroy }
327
+
328
+ it "should return the mixin types of a node" do
329
+ @node.mixin_types.should eql(["mix:created"])
330
+ end
331
+ end
332
+
333
+ describe "#add_mixin" do
334
+ before { @node = SafetyPin::Node.create("/content/foo") }
335
+ after { @node.destroy }
336
+
337
+ it "should add a mixin type to node" do
338
+ @node.add_mixin("mix:created")
339
+ @node.save
340
+ @node.mixin_types.should eql(["mix:created"])
341
+ end
342
+
343
+ it "should require a save before the mixin addition is detected" do
344
+ @node.add_mixin("mix:created")
345
+ @node.mixin_types.should eql([])
346
+ end
347
+ end
348
+
349
+ describe "#remove_mixin" do
350
+ before do
351
+ @node = SafetyPin::Node.create("/content/foo")
352
+ @node.add_mixin("mix:created")
353
+ @node.save
354
+ end
355
+
356
+ after { @node.destroy }
357
+
358
+ it "should remove a mixin type from a node" do
359
+ @node.mixin_types.should eql(["mix:created"])
360
+ @node.remove_mixin("mix:created")
361
+ @node.save
362
+ @node.mixin_types.should eql([])
363
+ end
364
+
365
+ it "should require a save before the mixin removal is detected" do
366
+ @node.remove_mixin("mix:created")
367
+ @node.mixin_types.should eql(["mix:created"])
368
+ @node.reload
369
+ end
370
+ end
371
+
372
+ describe ".build" do
373
+ context "given an absolute path" do
374
+ it "should build and return a property-less, unsaved nt:unstructured child node" do
375
+ node = SafetyPin::Node.build("/content/foo")
376
+ node.should be_new
377
+ node.properties.should eql({})
378
+ end
379
+
380
+ context "and a node type string" do
381
+ it "should create an unsaved node of the given type" do
382
+ node = SafetyPin::Node.build("/content/foo", "nt:folder")
383
+ node.should be_new
384
+ node["jcr:primaryType"].should eql("nt:folder")
385
+ end
386
+ end
387
+
388
+ context "that already exists" do
389
+ it "should raise an error" do
390
+ lambda { SafetyPin::Node.build("/content") }.should raise_error(SafetyPin::NodeError)
391
+ end
392
+ end
393
+ end
394
+
395
+ context "given an absolute path with a non-existent parent node" do
396
+ it "should raise an error" do
397
+ lambda { SafetyPin::Node.build("/content/foo/bar/baz") }.should raise_error(SafetyPin::NodeError)
398
+ end
399
+ end
400
+
401
+ context "given a relative path" do
402
+ it "should raise an error" do
403
+ lambda { SafetyPin::Node.build("content/foo") }.should raise_error(ArgumentError)
404
+ end
405
+ end
406
+ end
407
+
408
+ describe ".create" do
409
+ context "given a path" do
410
+ it "should build and save a node" do
411
+ node = SafetyPin::Node.create("/content/foo")
412
+ node.should be_a(SafetyPin::Node)
413
+ node.destroy
414
+ end
415
+ end
416
+
417
+ context "given a path and a node type" do
418
+ it "should build and save a node of the specified type" do
419
+ node = SafetyPin::Node.create("/content/foo", "nt:folder")
420
+ SafetyPin::Node.find("/content/foo").should_not be_nil
421
+ node.destroy
422
+ end
423
+ end
424
+ end
425
+
426
+ context "#value_factory" do
427
+ it "should return a value factory instance" do
428
+ SafetyPin::Node.find("/content").value_factory.should be_a(Java::JavaxJcr::ValueFactory)
429
+ end
430
+ end
431
+
432
+ describe "#property_is_multi_valued" do
433
+ it "should return true if property is multi-valued" do
434
+ node = SafetyPin::Node.create("/content/foo")
435
+ node["bar"] = ["baz", "qux"]
436
+ node.save
437
+ property = node.j_node.get_property("bar")
438
+ node.property_is_multi_valued?(property).should be_true
439
+ node.destroy
440
+ end
441
+
442
+ it "should return false if property is not multi-valued" do
443
+ node = SafetyPin::Node.create("/content/foo")
444
+ node["bar"] = "baz"
445
+ node.save
446
+ property = node.j_node.get_property("bar")
447
+ node.property_is_multi_valued?(property).should be_false
448
+ node.destroy
449
+ end
450
+ end
451
+
452
+ describe "#destroy" do
453
+ it "should remove node from JCR" do
454
+ path = "/content/foo"
455
+ node = SafetyPin::Node.create(path)
456
+ node.destroy
457
+ SafetyPin::Node.find(path).should be_nil
458
+ end
459
+
460
+ it "should save changes in parent node" do
461
+ parent_node = SafetyPin::Node.create("/content/foo")
462
+ node = SafetyPin::Node.create("/content/foo/bar")
463
+ parent_node["baz"] = "qux"
464
+ parent_node.should be_changed
465
+ node.destroy
466
+ parent_node.should_not be_changed
467
+ parent_node.destroy
468
+ end
469
+
470
+ context "when it fails" do
471
+ before do
472
+ @node = SafetyPin::Node.create("/content/foo")
473
+ @node.add_mixin("mix:created")
474
+ @node.save
475
+ end
476
+
477
+ after { @node.reload; @node.destroy }
478
+
479
+ it "should raise an error" do
480
+ @node.remove_mixin("mix:created") # make node unremoveable
481
+ lambda { @node.destroy }.should raise_error(SafetyPin::NodeError)
482
+ end
483
+ end
484
+ end
485
+
486
+ describe "#primary_type" do
487
+ before { @node = SafetyPin::Node.create("/content/foo") }
488
+ after { @node.destroy }
489
+
490
+ it "should return the primary type of the node" do
491
+ @node.primary_type.should eql("nt:unstructured")
492
+ end
493
+ end
494
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe SafetyPin::Query do
4
+ before(:all) do
5
+ SafetyPin::JCR.login(:hostname => "http://localhost:4502", :username => "admin", :password => "admin")
6
+ end
7
+
8
+ before do
9
+ SafetyPin::JCR.session.refresh(false)
10
+ end
11
+
12
+ after(:all) do
13
+ SafetyPin::JCR.logout
14
+ end
15
+
16
+ let(:query) { SafetyPin::Query.new }
17
+
18
+ describe ".execute" do
19
+ before do
20
+ @node = SafetyPin::Node.create("/content/foo")
21
+ @node["bar"] = "baz"
22
+ @node.save
23
+ end
24
+
25
+ after { @node.destroy }
26
+
27
+ it "should lookup nodes given a valid JCR-SQL2 query string" do
28
+ nodes = SafetyPin::Query.execute("SELECT * FROM [nt:base] WHERE [nt:base].bar IS NOT NULL")
29
+ nodes.first["bar"].should_not be_nil
30
+ end
31
+ end
32
+
33
+ describe "#sql" do
34
+ it "should compile a default query string to return all nodes" do
35
+ query.sql.should eql("SELECT * FROM [nt:base]")
36
+ end
37
+
38
+ it "should compile a query string to search for nodes of a specific type" do
39
+ query.type("cq:Page")
40
+ query.sql.should eql("SELECT * FROM [cq:Page]")
41
+ end
42
+
43
+ it "should compile a query string to search for a node with a property and value" do
44
+ query.where("foo" => "bar")
45
+ query.sql.should eql("SELECT * FROM [nt:base] WHERE [foo] = 'bar'")
46
+ end
47
+
48
+ it "should compile a query string to search for a node with multiple properties and values" do
49
+ query.where("foo" => "bar", "baz" => "quux")
50
+ query.sql.should eql("SELECT * FROM [nt:base] WHERE [foo] = 'bar' AND [baz] = 'quux'")
51
+ end
52
+
53
+ it "should compile a query string to search for a node within a path" do
54
+ query.within("/content/")
55
+ query.sql.should eql("SELECT * FROM [nt:base] WHERE [jcr:path] LIKE '/content/%'")
56
+ end
57
+
58
+ it "should compile a query string to search a node within multiple nodes" do
59
+
60
+ end
61
+ end
62
+
63
+ describe "#within" do
64
+ context "given string paths" do
65
+ it "should append to @within instance variable" do
66
+ query.within("/foo")
67
+ query.within("/bar")
68
+ query.instance_eval { @within }.should eql(["/foo", "/bar"])
69
+ end
70
+ end
71
+
72
+ context "given an array of string paths" do
73
+ it "should append to @within instance variable" do
74
+ query.within("/foo")
75
+ query.within(["/bar", "/baz"])
76
+ query.instance_eval { @within }.should eql(["/foo", "/bar", "/baz"])
77
+ end
78
+ end
79
+
80
+ context "given an invalid argument" do
81
+ it "should ignore it" do
82
+ query.within("/foo")
83
+ query.within(nil)
84
+ query.instance_eval { @within }.should eql(["/foo"])
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#where" do
90
+ it "should append to where_conditions" do
91
+ query.where("foo" => "bar")
92
+ query.where("baz" => "quux")
93
+ query.where_conditions.should eql([SafetyPin::Query::WhereCondition.new("foo", "bar"), SafetyPin::Query::WhereCondition.new("baz", "quux")])
94
+ end
95
+ end
96
+
97
+ describe "#type" do
98
+ it "should set the node type" do
99
+ query = SafetyPin::Query.new
100
+ query.type("cq:Page")
101
+ query.instance_eval { @type }.should eql("cq:Page")
102
+ end
103
+ end
104
+
105
+ describe "#where_conditions" do
106
+ it "should return an empty array by default" do
107
+ query.where_conditions.should eql([])
108
+ end
109
+ end
110
+
111
+ #
112
+ # it "should query for nodes beneath a specific path" do
113
+ # pending
114
+ # nodes = SafetyPin::Query.path("/content/sfu")
115
+ # end
116
+ #
117
+ # it "should query for nodes with a specific property" do
118
+ # pending
119
+ # nodes = SafetyPin::Query.where("cq:Template" => "/apps/sfu/templates/basicpage")
120
+ # end
121
+ #
122
+ # it "should chain together query methods to build a query" do
123
+ # pending
124
+ # nodes = SafetyPin::Query.path("/content").type("nt:unstructured").where("jcr:title" => "Open House")
125
+ # end
126
+ end
@@ -0,0 +1,3 @@
1
+ $:<<File.join(File.dirname(__FILE__), '/../lib')
2
+
3
+ require 'safety_pin'
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe SafetyPin::Query::WhereCondition do
4
+ it "should be instantiated with a name, value, and comparator" do
5
+ where_condition = SafetyPin::Query::WhereCondition.new("foo", "bar", "LIKE")
6
+ where_condition.should_not be_nil
7
+ end
8
+
9
+ it "should generate a SQL WHERE string fragment" do
10
+ where_condition = SafetyPin::Query::WhereCondition.new("foo", "bar", "LIKE")
11
+ where_condition.sql_fragment.should eql("[foo] LIKE 'bar'")
12
+ end
13
+
14
+ it "should be equal based on its main attributes" do
15
+ condition1 = SafetyPin::Query::WhereCondition.new("foo", "bar", "LIKE")
16
+ condition2 = SafetyPin::Query::WhereCondition.new("foo", "bar", "LIKE")
17
+ condition1.should == condition2
18
+ condition1.should eql(condition2)
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: safety_pin
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Jordan Raine
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-05-10 00:00:00 Z
14
+ dependencies: []
15
+
16
+ description: An easy-to-use JCR connector for JRuby
17
+ email:
18
+ - jnraine@gmail.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - .rspec
27
+ - .rvmrc
28
+ - Gemfile
29
+ - Gemfile.lock
30
+ - LICENSE
31
+ - README.md
32
+ - Rakefile
33
+ - lib/safety_pin.rb
34
+ - lib/safety_pin/jcr.rb
35
+ - lib/safety_pin/node.rb
36
+ - lib/safety_pin/query.rb
37
+ - lib/safety_pin/query/where_condition.rb
38
+ - lib/safety_pin/version.rb
39
+ - safety_pin.gemspec
40
+ - spec/jcr_spec.rb
41
+ - spec/jcr_sql2_spec.rb
42
+ - spec/node_spec.rb
43
+ - spec/query_spec.rb
44
+ - spec/spec_helper.rb
45
+ - spec/where_condition_spec.rb
46
+ homepage: ""
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.8.15
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: JCR connector for JRuby
73
+ test_files:
74
+ - spec/jcr_spec.rb
75
+ - spec/jcr_sql2_spec.rb
76
+ - spec/node_spec.rb
77
+ - spec/query_spec.rb
78
+ - spec/spec_helper.rb
79
+ - spec/where_condition_spec.rb