module Scrivito class CmsBackend VALID_INDEX_NAMES = %w[id path ppath permalink].freeze class << self def instance @instance ||= new end end def initialize @query_counter = 0 @caching = true end def begin_caching @caching = true end def end_caching CmsDataCache.clear_request_cache @caching = false end def clear_cache CmsDataCache.clear_request_cache end def caching? !!@caching end def query_counter @query_counter end # For test purpose only. def reset_query_counter! @query_counter = 0 end def 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 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 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 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 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 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 search_objs(workspace, params) cache_index = 'search' cache_key = params.to_param 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 def create_obj(workspace_id, attributes) write_obj(:post, "/workspaces/#{workspace_id}/objs", attributes) end def update_obj(workspace_id, obj_id, attributes) write_obj(:put, "/workspaces/#{workspace_id}/objs/#{obj_id}", attributes) end private def write_obj(verb, path, attributes) Backend::ObjDataFromRest.new(CmsRestApi.task_unaware_request(verb, path, attributes)) end def update_workspace_cache(id, cached_data_tag, changed_workspace, options) if changed_workspace workspace_data = changed_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 def fetch_cached_data_by_tag(data_tag) data_tag && CmsDataCache.read_data_from_tag(data_tag) end def build_workspace_data(raw_data, content_state_id) WorkspaceData.new(raw_data.merge('content_state_id' => content_state_id)) end def 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 def request_search_result_from_backend(workspace, params) params = params.merge(consistent_with: workspace.content_state_id) CmsRestApi.get("workspaces/#{workspace.id}/objs/search", params) end def request_blob_datas_from_backend(id, options) @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 def 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 def 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 def normalize_blob_id(id) CmsRestApi.normalize_path_component(id) end end end