safety_pin 0.0.1

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 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