module Scrivito module CmsBackend VALID_INDEX_NAMES = %w[id path ppath permalink].freeze def self.find_workspace_data_from_cache(id) cached_workspace_state = CmsDataCache.read_workspace_state(id) cached_data_tag = cached_workspace_state.try(:second) cached_content_state_id = cached_workspace_state.try(:first) if cached_data_tag && cached_content_state_id if raw_workspace_data = fetch_cached_data_by_tag(cached_data_tag) build_workspace_data(raw_workspace_data, cached_content_state_id) end end end def self.find_workspace_data_by_id(id, timeout=nil) options = timeout ? {timeout: timeout} : {} cached_workspace_state = CmsDataCache.read_workspace_state(id) cached_csid = cached_workspace_state.try(:first) cached_workspace_data_tag = cached_workspace_state.try(:second) changes = CmsRestApi.get("/workspaces/#{id}/changes", {from: cached_csid}, options) update_obj_cache(id, cached_csid, changes) raw_workspace_data, workspace_data_tag = update_workspace_cache( id, cached_workspace_data_tag, changes["workspace"], options) current_csid = changes["current"] current_workspace_state = [current_csid, workspace_data_tag] if current_workspace_state != cached_workspace_state CmsDataCache.write_workspace_state(id, current_workspace_state) end build_workspace_data(raw_workspace_data, current_csid) rescue Scrivito::ClientError => client_error if client_error.http_code == 404 nil else raise end end def self.find_obj_data_by(revision, index, keys) index = index.to_s if index == "id" obj_datas = Backend::ObjLoad.load(revision, keys) obj_datas.map { |obj_data| obj_data ? [obj_data] : [] } else index_implementation = Backend::Index.by_name(index) Backend::ObjQuery.query(revision, index_implementation, keys) end end def self.find_blob_data(id, access, verb, options = {}) if blob_data = find_blob_data_from_cache(id, access, verb, options) blob_data else id = normalize_blob_id(id) blob_datas = request_blob_datas_from_backend(id, options) store_blob_datas_in_cache(id, options, blob_datas) blob_datas[access][verb] end end def self.find_blob_data_from_cache(id, access, verb, options) cache_key = blob_data_cache_key(normalize_blob_id(id), access, verb, options) CmsDataCache.cache.read(cache_key) end def self.find_binary_meta_data(blob_id) blob_id = normalize_blob_id(blob_id) cache_key = "binary_meta_data/#{blob_id}" if meta_data = CmsDataCache.cache.read(cache_key) meta_data else meta_data = CmsRestApi.get("blobs/#{blob_id}/meta_data")['meta_data'] CmsDataCache.cache.write(cache_key, meta_data) meta_data end end def self.search_objs(workspace, params) cache_index = 'search' cache_key = search_params_cache_key(params) cache = Backend::ObjDataCache.view_for_revision(workspace.revision) if hit = cache.read_index(cache_index, cache_key) return hit end result = request_search_result_from_backend(workspace, params) persistent = !result.delete('tentative') cache.write_index_not_updatable(cache_index, cache_key, result, persistent: persistent) result end # For test purpose only. def self.query_counter @query_counter end # For test purpose only. def self.reset_query_counter! @query_counter = 0 end def self.write_obj(workspace_id, obj_id, attributes) path = "/workspaces/#{workspace_id}/objs/#{obj_id}" Backend::ObjDataFromRest.new(CmsRestApi.task_unaware_request(:put, path, attributes)) end def self.update_workspace_cache(id, cached_data_tag, workspace, options) if workspace workspace_data = workspace else if cached_workspace_data = fetch_cached_data_by_tag(cached_data_tag) workspace_data = cached_workspace_data workspace_data_tag = cached_data_tag else workspace_data = CmsRestApi.get("/workspaces/#{id}", nil, options) end end workspace_data_tag ||= CmsDataCache.write_data_to_tag(workspace_data) [workspace_data, workspace_data_tag] end private_class_method :update_workspace_cache def self.fetch_cached_data_by_tag(data_tag) data_tag && CmsDataCache.read_data_from_tag(data_tag) end private_class_method :fetch_cached_data_by_tag def self.build_workspace_data(raw_data, content_state_id) WorkspaceData.new(raw_data.merge('content_state_id' => content_state_id)) end private_class_method :build_workspace_data def self.update_obj_cache(workspace_id, cached_csid, changes) objs = changes["objs"] if objs.present? && objs != "*" last_state = Backend::ContentStateNode.find(cached_csid) changes_index = Backend::ObjDataCache.changes_index_from(objs) successor = last_state.create_successor(changes["to"], changes_index) cache = Backend::ObjDataCache.view_for_workspace(workspace_id, successor) changes_index.each do |id, tag| cache.write_obj_tag(id, tag) end end end private_class_method :update_obj_cache def self.request_search_result_from_backend(workspace, params) @query_counter ||= 0 @query_counter += 1 params = params.merge(consistent_with: workspace.content_state_id) CmsRestApi.get("workspaces/#{workspace.id}/objs/search", params) end private_class_method :request_search_result_from_backend def self.request_blob_datas_from_backend(id, options) @query_counter ||= 0 @query_counter += 1 case when transformation_definition = options[:transformation_definition] CmsRestApi.get("blobs/#{id}/transform", transformation: transformation_definition) when options[:no_cache] CmsRestApi.get("blobs/#{id}/no_cache") else CmsRestApi.get("blobs/#{id}") end end private_class_method :request_blob_datas_from_backend def self.store_blob_datas_in_cache(id, options, blob_datas) %w[public_access private_access].each do |access| %w[get head].each do |verb| if access_blob_data = blob_datas[access] if blob_data = access_blob_data[verb] cache_key = blob_data_cache_key(id, access, verb, options) CmsDataCache.cache.write(cache_key, blob_data, expires_in: blob_data['maxage']) end end end end end private_class_method :store_blob_datas_in_cache def self.blob_data_cache_key(id, access, verb, options) cache_key = "blob_data/#{id}/#{access}/#{verb}" if transformation_definition = options[:transformation_definition] cache_key << "/#{transformation_definition.to_query}" end if options[:no_cache] cache_key << '/no_cache' end cache_key end private_class_method :blob_data_cache_key def self.search_params_cache_key(params) MultiJson.encode(normalize_search_param(params)) end private_class_method :search_params_cache_key def self.normalize_search_param(param) case param when Array then param.map { |v| normalize_search_param(v) } when Hash then param.map { |k, v| [k.to_s, normalize_search_param(v)] }.sort else param end end private_class_method :normalize_search_param def self.normalize_blob_id(id) CmsRestApi.normalize_path_component(id) end private_class_method :normalize_blob_id end end