require 'restclient' require 'active_support/all' module RailsConnector # Provides a simple wrapper for the CMS Rest API. # # @example Request the published workspace: # RailsConnector::CmsRestApi.get('workspaces/published') # # @example Create a new Obj: # RailsConnector::CmsRestApi.post('revisions/001384beff9e5845/objs', # {'obj' => {'_path' => '/new_obj', '_obj_class' => 'Publication'}}) # # @example Update an Obj: # RailsConnector::CmsRestApi.put('revisions/001384beff9e5845/objs/9e432077f0412a63', # {'obj' => {'title' => 'new title'}}) # # @example Delete an Obj: # RailsConnector::CmsRestApi.delete('revisions/001384beff9e5845/objs/f4123622ff07b70b') # # @example Specify a poll interval (in seconds; default: 2) to use in case the response # is a task reference response and the final response is polled for: # RailsConnector::CmsRestApi.put('workspace/rtc/publish', nil, :interval => 10) # # @example Return immediately with the first response (without polling in case it is a # task reference response): # RailsConnector::CmsRestApi.task_unaware_request(:put, 'workspace/rtc/publish', nil) # class CmsRestApi DEFAULT_PROTOCOL = 'https'.freeze METHOD_TO_NET_HTTP_CLASS = { :get => Net::HTTP::Get, :put => Net::HTTP::Put, :post => Net::HTTP::Post, :delete => Net::HTTP::Delete, }.freeze class Configuration attr_accessor :url attr_accessor :login attr_accessor :api_key attr_accessor :http_host attr_accessor :tenant_name def url if @url.nil? raise RailsConnectorError, 'CmsRestApi configuration key "url" is missing. '\ 'Set "RailsConnector::Configuration.cms_url = "' end @url end end cattr_accessor :configuration do Configuration.new end def self.get(resource_path, payload = nil, options = nil) request_cms_api(:get, resource_path, payload, options) end def self.put(resource_path, payload, options = nil) request_cms_api(:put, resource_path, payload, options) end def self.post(resource_path, payload, options = nil) request_cms_api(:post, resource_path, payload, options) end def self.delete(resource_path, payload = nil, options = nil) request_cms_api(:delete, resource_path, payload, options) end def self.task_unaware_request(method, resource_path, payload = nil) raise "Unexpected method #{method}" unless [:delete, :get, :post, :put].include?(method) response_for_request_cms_api(method, resource_path, payload) end # This method is mainly for tests def self.reset! @connection_manager = nil end class << self private def request_cms_api(action, resource_path, payload, options) reset_connection_manager if connection_manager.uri != uri decoded = response_for_request_cms_api(action, resource_path, payload) return decoded unless Hash === decoded return decoded unless decoded.keys == ["task"] task_data = decoded["task"] return decoded unless Hash === task_data task_path = "tasks/#{task_data["id"]}" final_response(task_path, options) end def response_for_request_cms_api(method, resource_path, payload = nil) request = method_to_net_http_class(method).new(path(resource_path)) set_headers(request) request.body = MultiJson.encode(payload) if payload.present? handle_response(connection_manager.request(request)) end def handle_response(response) code = response.code.to_i if code.to_s.start_with?('2') MultiJson.load(response.body) elsif response.code == '403' raise AccessDenied.new(response.body) elsif (400...500).include?(code) begin specific_output = MultiJson.decode(response.body)['error'] raise ClientError.new(specific_output, code) rescue MultiJson::DecodeError raise BackendNotAvailable.new(response.body, code) end else raise BackendNotAvailable.new(response.body, code) end end def final_response(task_path, options) options ||= {} wait = options[:interval].presence.try(:to_f) || 2 task_data = response = nil loop do sleep wait task_data = response_for_request_cms_api(:get, task_path, nil) break unless task_data["status"] == "open" end return task_data["result"] if task_data["status"] == "success" message = task_data["message"] || "Missing error message in task response #{task_data}" raise ClientError.new(message, 400) end def set_headers(request) request.basic_auth(configuration.login, configuration.api_key) request['Content-type'] = 'application/json' request['Accept'] = 'application/json' request['Host'] = configuration.http_host if configuration.http_host.present? end def path(path) "/#{path}".squeeze('/') end def connection_manager @connection_manager ||= ConnectionManager.new(uri) end def uri url = configuration.url url = "#{DEFAULT_PROTOCOL}://#{url}" unless url.match /^http/ URI.parse(url) end def method_to_net_http_class(method) METHOD_TO_NET_HTTP_CLASS.fetch(method) end def reset_connection_manager @connection_manager = ConnectionManager.new(uri) end end end end