ecircle_soap_client 0.0.5

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