fixes in generating job and cache IDs
[opensuse:yast-rest-service.git] / webservice / lib / yast_cache.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 require 'digest/md5'
20
21 module Kernel
22 private
23   def model_symbol(object)
24     if object.instance_of? Class
25       object.to_s.to_sym
26     else
27       object.class.to_s.to_sym
28     end
29   end
30 end
31
32 class YastCache
33
34   include Singleton
35
36   def YastCache.active; @active ||= false; end
37   def YastCache.active= a; @active = a; end
38   def YastCache.job_queue_enabled?; YastCache.active; end
39
40   def YastCache.key(model, method, *args)
41     unless args.empty?
42       return "#{model.to_s.downcase}:#{method.to_s.downcase}:#{args}"
43     else
44       return "#{model.to_s.downcase}:#{method.to_s.downcase}"
45     end        
46   end
47
48   # returns reals method name
49   def YastCache.has_find_method(model_name)
50     model_name.capitalize!
51     object = Object.const_get((model_name).classify) rescue $!
52     if object.class == NameError && model_name.end_with?("s")
53       #trying real "s" like "dn" -> "dns", "kerbero" -> "kerberos",...
54       model_name = (model_name).classify + "s"
55       object = Object.const_get(model_name) rescue $!
56     else
57       model_name = (model_name).classify
58     end
59     if object.class != NameError && object.respond_to?(:find)
60       arguments = object.method(:find).arity != 0  ? :all : nil
61       return model_name, arguments
62     end    
63     return nil
64   end
65
66   def YastCache.find_key(model_name, key = :all)
67     mod_name, dummy = YastCache.has_find_method(model_name)
68     return nil if mod_name.blank?
69     object = Object.const_get(mod_name) rescue $!
70     if object.method(:find).arity != 0 
71       #has :all parameter
72       return YastCache.key(mod_name, :find, key)
73     else
74       return YastCache.key(mod_name, :find)
75     end
76   end
77
78   def YastCache.reset(calling_object, *arguments)
79     YastCache.reset_and_restart(calling_object, 0, true, *arguments)
80   end
81
82   def YastCache.reset_and_restart(calling_object, delay, delete_cache, *arguments)
83     unless YastCache.active
84 #      Rails.logger.debug "YastCache.reset: Cache is not active"
85       return
86     end
87     model = model_symbol(calling_object)
88     if !arguments.empty? && arguments[0] !=  :all
89       #reset also find.all caches
90       YastCache.reset_and_restart(calling_object, delay, delete_cache, :all)
91     end
92     key = YastCache.key(model, :find, arguments)
93     Rails.cache.delete(key) if delete_cache
94     jobs = Delayed::Job.find(:all)
95     start_job = true
96     jobs.each { |job|
97       data = YAML.load job.handler
98       if !arguments.empty? #all args
99         found = model == data[:class_name] &&
100                 :find == data[:method] &&
101                 arguments == data[:arguments]
102       else
103         found = model == data[:class_name] &&
104                 :find == data[:method] &&
105                 data[:arguments].empty?
106       end
107       if found 
108         if delete_cache
109           job.run_at = Time.now #set starttime to now in order to fill cache as fast as possible
110           job.save
111         end
112         Rails.logger.info("Job #{key} already inserted")
113         start_job = false
114       end
115     }
116     if start_job
117       Rails.logger.info("Inserting job #{key}")
118       unless arguments.empty?
119         PluginJob.run_async((delay).seconds.from_now, model, :find, *arguments) 
120       else
121         PluginJob.run_async((delay).seconds.from_now, model, :find)
122       end
123     end
124   end
125
126   def YastCache.delete(calling_object, *arguments)
127     unless YastCache.active
128 #      Rails.logger.debug "YastCache.delete: Cache is not active"
129       return
130     end
131     cache_key = YastCache.key(model_symbol(calling_object), :find, arguments)
132     Rails.cache.delete(cache_key)
133
134     #finding involved keys e.g. user:find:<id> includes user:find::all
135     YastCache.reset(calling_object, :all)
136   end
137     
138   def YastCache.fetch(calling_object, *options)
139
140     unless YastCache.active
141 #      Rails.logger.debug "YastCache.fetch: Cache is not active"
142       if  block_given?
143         return yield
144       else
145         Rails.logger.error "YastCache.fetch: No block is given"       
146         return nil
147       end
148     end
149     key = YastCache.key(model_symbol(calling_object), :find, options)
150     job_delay = 3
151     raised_exception = nil
152     re_load = Rails.cache.exist?(key) ?  true : false
153     if  block_given?
154       ret = Rails.cache.fetch(key) {
155         block_ret = nil
156         begin
157           block_ret = yield
158           if block_ret.blank?
159             #no data found -> remove entry from the cache table
160             cache_data = DataCache.find_by_path key
161             cache_data.each { |cache|
162               cache.destroy
163             } unless cache_data.blank? 
164           else
165             #update MD5 if needed
166             md5 = Digest::MD5.hexdigest(block_ret.to_json)
167             cache_data = DataCache.find_by_path key
168             cache_data.each { |cache|
169               if cache.refreshed_md5.blank? || cache.refreshed_md5 != md5
170                 cache.refreshed_md5 = md5
171                 cache.picked_md5 = md5 if cache.picked_md5.blank?
172                 cache.save
173               end
174             } unless cache_data.blank? 
175           end
176         rescue Exception => raised_exception
177           Rails.logger.error "YastCache.fetch(#{key}) failed: #{raised_exception.inspect}"        
178           if re_load
179             Rails.logger.error "Trying again in #{job_delay} seconds"
180           else
181             raise raised_exception #should be shown to the user
182           end
183         end
184         block_ret
185       }
186       if ret.blank?
187         Rails.cache.delete(key)
188         Rails.logger.debug "deleting empty cache #{key} #{!Rails.cache.exist?(key)}"
189       end
190     else
191       ret = Rails.cache.fetch(key)
192       md5 = Digest::MD5.hexdigest(ret.to_json)
193       cache_data = DataCache.find_by_path key
194       cache_data.each { |cache|
195         if cache.picked_md5.blank? || cache.picked_md5 != md5
196           cache.picked_md5 = md5
197           cache.save
198         end
199       } unless cache_data.blank? 
200     end
201     delete_cache = false
202     YastCache.reset_and_restart(calling_object,job_delay,delete_cache,*options) if re_load #add reload into the job queue
203     return ret if ret.nil?
204     ret.dup #has to be dup cause the cache value is frozen
205   end
206 end
207