ecircle_soap_client 0.0.5

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create ree-1.8.7-2011.03@ecirclegemset
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ # -*- mode: ruby -*-
2
+ source "http://rubygems.org"
3
+
4
+ gem 'rake', "= 0.8.7"
5
+ gem 'savon'
6
+ gem 'nokogiri'
7
+ gem 'multipart-post', :git => 'git://github.com/gorenje/multipart-post.git'
8
+ gem 'uuidtools'
9
+
10
+ group :development do
11
+ gem 'pry'
12
+ gem 'pry-doc'
13
+ gem 'gist'
14
+ gem "thoughtbot-shoulda", ">= 0"
15
+ gem "bundler", "~> 1.0.0"
16
+ gem "jeweler", "~> 1.6.4"
17
+ gem "rcov", ">= 0"
18
+ gem 'rr'
19
+ gem "cheat"
20
+ end
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ eCircle SOAP Client
2
+ ===================
3
+
4
+ Warning
5
+ -------
6
+ Experimental code and not yet complete. Lots of testing are missing and functionality is limited.
7
+ Simple things like creating and deleting users and groups is possible, but message creation and
8
+ sending are not yet supported.
9
+
10
+ Description
11
+ -----------
12
+
13
+ Based on [Savon](http://savonrb.com/) and [Nokogiri](http://nokogiri.org/) and provides a simple interface for accessing the eCircle [WSDL synchronous API](http://www.soapclient.com/soapclient?template=%2Fclientform.html&fn=soapform&SoapTemplate=%2FSoapResult.html&SoapWSDL=http%3A%2F%2Fwebservices.ecircle-ag.com%2Fsoap%2Fecm.wsdl&_ArraySize=2).
14
+
15
+ Intention is to provide a ActiveRecord/ActiveModel-like interface to the individual entities provided by the SOAP API. It's far from complete and you can only do a few selected things with this gem.
16
+
17
+ Installation
18
+ ------------
19
+
20
+ Currently only via bundle with:
21
+
22
+ ```
23
+ gem 'ecircle_soap_client', :git => 'git://github.com/gorenje/ecircle_soap_client.git'
24
+ ```
25
+
26
+ Usage
27
+ -----
28
+
29
+ Configration:
30
+
31
+ ```
32
+ Ecircle.configure do |config|
33
+ config.user = "your@user.email.com"
34
+ config.realm = "http://example.com"
35
+ config.password = "supersecret"
36
+ end
37
+ ```
38
+
39
+ the WSDL settings are assumed to be
40
+
41
+ ```
42
+ wsdl.document = "http://webservices.ecircle-ag.com/soap/ecm.wsdl"
43
+ wsdl.endpoint = "http://webservices.ecircle-ag.com/rpc"
44
+ wsdl.namespace = "http://webservices.ecircleag.com/rpcns"
45
+ ```
46
+
47
+ but can be overridden in the configuration
48
+
49
+ ```
50
+ Ecircle.configure do |config|
51
+ config.wsdl.document = "http://example.com/?wsdl"
52
+ config.wsdl.endpoint = "http://blah.com"
53
+ config.wsdl.namespace = "some new namespace"
54
+ end
55
+ ```
56
+
57
+ Having configured the client, User retrieval is done via email or id:
58
+
59
+ ```
60
+ user = Ecircle::User.find_by_email("some@email.com")
61
+ user = Ecircle::User.find_by_id("1234131")
62
+ ```
63
+
64
+ Logon is handled automagically by the Ecircle::Client, however the caller is responsible for
65
+ retrying requests if a Ecircle::Client::NotLoggedIn exception is raised. There no need to
66
+ explicitly call Ecircle::Client#logon, the exception notifies the client that it's not longer
67
+ logged in and needs to login before the next request.
68
+
69
+ Groups and Members
70
+ ------------------
71
+
72
+ Having gotten a user, it's possible to find their memeberships of groups
73
+
74
+ ```
75
+ groups = user.membership
76
+ ```
77
+
78
+ Now a new user can be added to a group
79
+
80
+ ```
81
+ member = groups.first.add_member Ecircle::User.create_by_email( "another@email.com" )
82
+ ```
83
+
84
+ But if the member should be deleted from the group again
85
+
86
+ ```
87
+ member.delete
88
+ ```
89
+
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "ecircle_soap_client"
18
+ gem.homepage = "http://google.com/search?q=ecircle"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Ecircle SOAP client for synchron API}
21
+ gem.description = %Q{See summary.}
22
+ gem.email = "gerrit.riessen@gmail.com"
23
+ gem.authors = ["Gerrit Riessen"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ require 'rcov/rcovtask'
36
+ Rcov::RcovTask.new do |test|
37
+ test.libs << 'test'
38
+ test.pattern = 'test/**/test_*.rb'
39
+ test.verbose = true
40
+ test.rcov_opts << '--exclude "gems/*"'
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "ecircle_soap_client #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.5
@@ -0,0 +1,100 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "ecircle_soap_client"
8
+ s.version = "0.0.5"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Gerrit Riessen"]
12
+ s.date = "2011-09-01"
13
+ s.description = "See summary."
14
+ s.email = "gerrit.riessen@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".document",
20
+ ".rvmrc",
21
+ "Gemfile",
22
+ "README.md",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "ecircle_soap_client.gemspec",
26
+ "lib/ecircle/base.rb",
27
+ "lib/ecircle/client.rb",
28
+ "lib/ecircle/configuration.rb",
29
+ "lib/ecircle/group.rb",
30
+ "lib/ecircle/member.rb",
31
+ "lib/ecircle/message.rb",
32
+ "lib/ecircle/user.rb",
33
+ "lib/ecircle_soap_client.rb",
34
+ "start_pry_with_ecircle",
35
+ "test/.login.yml.sample",
36
+ "test/helper.rb",
37
+ "test/test_ecircle_soap_client.rb",
38
+ "test/test_ecircle_soap_client_group.rb",
39
+ "test/test_ecircle_soap_client_member.rb",
40
+ "test/test_ecircle_soap_client_message.rb",
41
+ "test/test_ecircle_soap_client_user.rb"
42
+ ]
43
+ s.homepage = "http://google.com/search?q=ecircle"
44
+ s.licenses = ["MIT"]
45
+ s.require_paths = ["lib"]
46
+ s.rubygems_version = "1.8.10"
47
+ s.summary = "Ecircle SOAP client for synchron API"
48
+
49
+ if s.respond_to? :specification_version then
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
53
+ s.add_runtime_dependency(%q<rake>, ["= 0.8.7"])
54
+ s.add_runtime_dependency(%q<savon>, [">= 0"])
55
+ s.add_runtime_dependency(%q<nokogiri>, [">= 0"])
56
+ s.add_runtime_dependency(%q<multipart-post>, [">= 0"])
57
+ s.add_runtime_dependency(%q<uuidtools>, [">= 0"])
58
+ s.add_development_dependency(%q<pry>, [">= 0"])
59
+ s.add_development_dependency(%q<pry-doc>, [">= 0"])
60
+ s.add_development_dependency(%q<gist>, [">= 0"])
61
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
62
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
63
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
64
+ s.add_development_dependency(%q<rcov>, [">= 0"])
65
+ s.add_development_dependency(%q<rr>, [">= 0"])
66
+ s.add_development_dependency(%q<cheat>, [">= 0"])
67
+ else
68
+ s.add_dependency(%q<rake>, ["= 0.8.7"])
69
+ s.add_dependency(%q<savon>, [">= 0"])
70
+ s.add_dependency(%q<nokogiri>, [">= 0"])
71
+ s.add_dependency(%q<multipart-post>, [">= 0"])
72
+ s.add_dependency(%q<uuidtools>, [">= 0"])
73
+ s.add_dependency(%q<pry>, [">= 0"])
74
+ s.add_dependency(%q<pry-doc>, [">= 0"])
75
+ s.add_dependency(%q<gist>, [">= 0"])
76
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
77
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
78
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
79
+ s.add_dependency(%q<rcov>, [">= 0"])
80
+ s.add_dependency(%q<rr>, [">= 0"])
81
+ s.add_dependency(%q<cheat>, [">= 0"])
82
+ end
83
+ else
84
+ s.add_dependency(%q<rake>, ["= 0.8.7"])
85
+ s.add_dependency(%q<savon>, [">= 0"])
86
+ s.add_dependency(%q<nokogiri>, [">= 0"])
87
+ s.add_dependency(%q<multipart-post>, [">= 0"])
88
+ s.add_dependency(%q<uuidtools>, [">= 0"])
89
+ s.add_dependency(%q<pry>, [">= 0"])
90
+ s.add_dependency(%q<pry-doc>, [">= 0"])
91
+ s.add_dependency(%q<gist>, [">= 0"])
92
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
93
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
94
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
95
+ s.add_dependency(%q<rcov>, [">= 0"])
96
+ s.add_dependency(%q<rr>, [">= 0"])
97
+ s.add_dependency(%q<cheat>, [">= 0"])
98
+ end
99
+ end
100
+
@@ -0,0 +1,40 @@
1
+ module Ecircle
2
+ class Base
3
+ attr_reader :id, :all_fields, :named_attrs
4
+
5
+ def id=(value)
6
+ @id = value
7
+ end
8
+
9
+ def [](name)
10
+ @all_fields[name.to_sym]
11
+ end
12
+
13
+ def initialize
14
+ @id, @all_fields, @named_attrs = "", {}, {}
15
+ end
16
+
17
+ def init_with_xml(element_name, xml_string)
18
+ n = Nokogiri.parse(xml_string)
19
+ @id = n.xpath("#{element_name}/@id" ).to_s
20
+ @all_fields = Hash[ n.xpath("//#{element_name}/*").collect do |a|
21
+ [a.name.to_sym, a.children.first.to_s]
22
+ end ]
23
+ @named_attrs = Hash[ n.xpath("#{element_name}/namedattr").collect do |a|
24
+ [a.attributes["name"].value,
25
+ a.children.empty? ? "" : a.children.first.to_s]
26
+ end ]
27
+ end
28
+
29
+ # Handle all assignments, everything else is propagated to super.
30
+ def method_missing(method, *args, &block)
31
+ case method.to_s
32
+ when /\[\]=/ then super
33
+ when /(.+)=/
34
+ @all_fields[$1.to_sym] = args.first
35
+ else
36
+ super
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,145 @@
1
+ module Ecircle
2
+ class Client
3
+ attr_reader :session_token
4
+
5
+ LoginFailed = Class.new(RuntimeError)
6
+ NotLoggedIn = Class.new(RuntimeError)
7
+ PermissionDenied = Class.new(RuntimeError)
8
+
9
+ def initialize
10
+ @session_token = nil
11
+ end
12
+
13
+ class << self
14
+ # helper method for allowing clients to automatically retry requests to
15
+ # ecircle.
16
+ def attempt(retries = 2, &block)
17
+ begin
18
+ yield
19
+ rescue Ecircle::Client::NotLoggedIn => e
20
+ raise e unless retries > 0
21
+ attempt(retries - 1, &block)
22
+
23
+ rescue Ecircle::Client::PermissionDenied => e
24
+ raise e unless retries > 0
25
+ Ecircle.client.logon
26
+ attempt(retries - 1, &block)
27
+
28
+ rescue NoMethodError => e
29
+ raise e unless retries > 0
30
+ ## FIXME horrible hack because ecircle sometimes forgets that they
31
+ ## FIXME have a FindMembershipsByEmail method - old software with bad memory?
32
+ if e.message =~ /No such operation 'FindMembershipsByEmail'/
33
+ attempt(retries - 1, &block)
34
+ else
35
+ raise e
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # This is the magic. All we do here is take the method name, camelcase it and
42
+ # then pass it to eCircle. It is assumed that the args array contains a hash
43
+ # as the first element. This then contains the arguments for the call. E.g.:
44
+ # Ecircle.client.lookup_user_by_email :email => "willy@wonka.net"
45
+ # This will do a number of things:
46
+ # 1. login if session token is undefined
47
+ # 2. set the session parameter along with the email argument
48
+ # 3. check for specific eCircle exceptions and attempt to handle them.
49
+ def method_missing(method, *args, &block)
50
+ logon if @session_token.nil?
51
+
52
+ method_name, body = method.to_s.camelcase, args.first || {}
53
+ body[:session] = @session_token
54
+
55
+ response = begin
56
+ client.request(method_name) { soap.body = body }
57
+ rescue Savon::SOAP::Fault => e
58
+ handle_savon_fault(e, :for_method => method)
59
+ end
60
+
61
+ data = response.
62
+ body["#{method}_response".to_sym]["#{method}_return".to_sym]
63
+
64
+ if block_given?
65
+ yield(data)
66
+ else
67
+ case data
68
+ when /^[<]user/ then Ecircle::User.new(data)
69
+ when /^[<]member/ then Ecircle::Member.new(data)
70
+ when Hash
71
+ # If the response is only an empty element, the data will look like this:
72
+ # {:"@xmlns:ns1"=>"http://webservices.ecircleag.com/rpcns"}
73
+ # Return nil if we only have attributes.
74
+ data.all? { |key, value| key.to_s =~ /^@/ } ? nil : data
75
+ else
76
+ data
77
+ end
78
+ end
79
+ end
80
+
81
+ # Generate a Savon::Client to use for making requests. The configuration for the
82
+ # client is taken from the configure object.
83
+ def client
84
+ @client ||= Savon::Client.new do
85
+ wsdl.document = Ecircle.configure.wsdl.document
86
+ wsdl.endpoint = Ecircle.configure.wsdl.endpoint
87
+ wsdl.namespace = Ecircle.configure.wsdl.namespace
88
+ end
89
+ end
90
+
91
+ # Send logon request and store the session token if successful.
92
+ # The session token is then passed into each subsequent request to
93
+ # eCircle.
94
+ def logon
95
+ @session_token = (client.request :logon do
96
+ soap.body = {
97
+ :user => Ecircle.configure.user,
98
+ :realm => Ecircle.configure.realm,
99
+ :passwd => Ecircle.configure.password
100
+ }
101
+ end).body[:logon_response][:logon_return].to_s
102
+ rescue Savon::SOAP::Fault => e
103
+ @session_token = nil
104
+ raise LoginFailed, "Msg: #{e.message}"
105
+ end
106
+
107
+ # Invalid the session token and set it to nil.
108
+ # The session token automagically expires after 10 minutes of inactivity, so
109
+ # you might have to logout if methods are with NotLoggedIn
110
+ def logout
111
+ client.request :logout do
112
+ soap.body = {
113
+ :session => @session_token
114
+ }
115
+ end
116
+ ensure
117
+ @session_token = nil
118
+ end
119
+
120
+ private
121
+
122
+ def handle_savon_fault(exception, opts = {})
123
+ method = opts[:for_method] || "UNKNOWN"
124
+
125
+ case exception.message
126
+
127
+ when /No such operation/ then
128
+ raise NoMethodError, "#{method} (by way of #{exception.message})"
129
+
130
+ when /Not authenticated/, /LoginException/ then
131
+ @session_token = nil # automagically login with the next call.
132
+ raise NotLoggedIn, exception.message
133
+
134
+ when /Authorisation failure/, /Permission Problem/ then
135
+ # This is not a login issue per-se, this is an authorisation
136
+ # issue, so the client must decide whether to logon again to
137
+ # attempt to resolve the issue.
138
+ raise PermissionDenied, exception.message
139
+
140
+ else
141
+ raise exception
142
+ end
143
+ end
144
+ end
145
+ end