merged cont.
[opensuse:yast-rest-service.git] / webservice / lib / resource_registration.rb
1 #--
2 # Webyast Webservice framework
3 #
4 # Copyright (C) 2009, 2010 Novell, Inc. 
5 #   This library is free software; you can redistribute it and/or modify
6 # it only under the terms of version 2.1 of the GNU Lesser General Public
7 # License as published by the Free Software Foundation. 
8 #
9 #   This library is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 
12 # details. 
13 #
14 #   You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software 
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 #++
18
19 # load resources and populate database
20
21 class ResourceRegistrationError < StandardError
22 end
23
24 class ResourceRegistrationPathError < ResourceRegistrationError
25 end
26
27 class ResourceRegistrationFormatError < ResourceRegistrationError
28 end
29
30 class ResourceRegistration
31  
32   @@in_production = (ENV['RAILS_ENV'] == "production")
33   
34   @@resources = Hash.new
35   def self.resources
36     @@resources
37   end
38   
39 private
40   def self.error msg
41     if @@in_production
42       log.error msg
43       return
44     else
45       raise ResourceRegistrationFormatError.new( msg )
46     end
47   end
48   
49 public  
50   #
51   # reset registered resources
52   # useful for testing
53   #
54   def self.reset
55     @@resources = Hash.new
56   end
57
58   # register a (.yaml) resource description
59   #
60   # optionally the interface and controller can be passed
61   # otherwise they are read from the yml file
62   #
63   
64   def self.register(file, interface = nil, controller = nil)
65     require 'yaml'
66     name = name || File.basename(file, ".*")
67     begin
68       resource = YAML.load(File.open(file)) || Hash.new
69     rescue Exception => e
70       $stderr.puts "#{file} failed to load: #{$!}"
71       raise # re-raise
72     end
73
74     error "#{file} has wrong format" unless resource.is_a? Hash
75     
76     # interface: can override
77     interface = resource['interface'] || interface
78     error "#{file} does not specify interface" unless interface
79     error "#{file}: interface is not a qualified name" unless interface =~ %r{((\w+)\.)+(\w+)}
80    
81     name = interface.split(".").pop
82     
83     # controller: must be given
84     controller = resource['controller'] || controller
85     error "#{file} does not specify controller" unless controller
86 #    error "#{file}: controller is not a path name" unless controller =~ %r{((\w+)/)+(\w+)}
87     
88     # policy: is optional, interface is used otherwise
89     policy = resource["policy"]
90
91     # singular: is optional, defaults to false
92     singular = resource["singular"] || false
93
94     # cache_enabled: is optional, default to true
95     unless resource["cache"].blank?
96       cache_enabled = resource["cache"]["enabled"]
97     else
98       cache_enabled =  true
99     end
100
101     # cache_priority: is optional, default to -10
102     cache_priority = (resource["cache"]["priority"].to_i unless resource["cache"].blank?) || -10
103
104     # cache_reload_after: is optional, default to 0 (no reload)
105     cache_reload_after = (resource["cache"]["reload_after"].to_i unless resource["cache"].blank?) || 0
106
107     # cache_arguments: is optional, default to ""
108     cache_arguments = (resource["cache"]["arguments"] unless resource["cache"].blank? ) || ""
109
110     error "#{file}: has non-plural interface #{interface} without being flagged as singular" if !singular and name != name.pluralize
111
112     # nested: is optional, defaults to nil
113     nested = resource["nested"]
114     error "#{file}: singular resources don't support nested" if singular and nested
115
116     resources[interface] ||= Array.new
117     resources[interface] << { :controller => controller, :singular => singular, :nested => nested, :policy => policy,
118                               :cache_enabled => cache_enabled, :cache_priority => cache_priority, :cache_reload_after => cache_reload_after,
119                               :cache_arguments => cache_arguments }
120   end
121
122   # register routes from a plugin
123   #
124   def self.register_plugin(plugin)
125     res_path = File.join(plugin.directory, 'config')
126     if defined? RESOURCE_REGISTRATION_TESTING
127       raise ResourceRegistrationPathError.new("Could not access plugin directory: #{res_path}") unless File.exists?( res_path )
128     end
129 #    $stderr.puts "checking #{res_path}"
130     Dir.glob(File.join(res_path, 'routes.rb')).each do |route|
131       basename = File.basename(plugin.directory)
132       raise ResourceRegistrationFormatError.new "Plugin #{basename} does private routing, please remove #{basename}/config/routes.rb."
133     end
134     res_path = File.join(res_path, 'resources')
135     if defined? RESOURCE_REGISTRATION_TESTING
136       raise ResourceRegistrationPathError.new("Could not access plugin directory: #{res_path}") unless File.exists?( res_path )
137     end
138 #    $stderr.puts "self.register_plugin #{res_path}"
139     registration_count = 0
140     Dir.glob(File.join(res_path, '**/*.y*ml')).each do |descriptor|
141 #      $stderr.puts "checking #{descriptor}"
142       next unless descriptor =~ %r{#{res_path}/((\w+)/)?(\w+)\.y(a)?ml$}
143 #      $stderr.puts "registering #{descriptor}"
144       self.register(descriptor)
145       registration_count += 1
146     end
147     if defined? RESOURCE_REGISTRATION_TESTING
148       raise ResourceRegistrationPathError.new("Could not find any YAML file with resource description below #{res_path}") unless registration_count > 0
149     end
150   end
151
152   # routes resources
153   #
154   def self.route resources
155     return unless resources
156     return if resources.empty?
157     
158     ActionController::Routing::Routes.draw do |map|
159 #map.root :controller => "resources", :action => "index"
160       resources.each do |interface,implementations|
161         
162         implementations.each do |implementation|
163         
164           # url and controller are closely coupled
165         
166           # so we split the controller path and use every path element but the last one as routing namespaces
167           # the last one specifies the resource name and thus the controller name
168           #
169           namespaces = implementation[:controller].split "/"
170           name = namespaces[-1]
171         
172           # the .namespace call affects the URI _and_ the controller path (!)
173         
174           toplevel = map
175           while namespaces.size > 1
176             toplevel.namespace(namespaces.shift) do |ns|
177               toplevel = ns
178             end
179           end
180           params = [ name, { :controller => namespaces.join("/"), :except => [ :new, :edit ],
181             :requirements => {:id => /[^\/]*(?=\.html|\.xml|\.json)|.+/ } } ]
182
183           if implementation[:singular]
184             toplevel.resource *params
185           else
186             toplevel.resources *params do |mapping|
187               nested = implementation[:nested] and mapping.resources(nested)
188             end
189           end
190         end
191       end
192     end  
193   end
194
195 end # class ResourceRegistration