module PIM

Constants

DISABLE_EXECUTION_GUARD_THREAD_KEY

Thread variable that tells whether the execution guard is switched off for the request running on a thread, see {__before_request}. It is a thread variable, not a fiber-local one, so the Fibers and Enumerators that a request starts see it as well.

DefaultExportFormats

Create Enum type containing all known export formats

EmptyToNilHashAttribute

Special Hash type attribute which will always return ‘nil’ if empty in json

EmptyToNilSetAttribute

Special Set type attribute which will always return ‘nil’ if empty in json

InternalSystemHashAttribute

Internal system attribute of base class ‘Hash’ which does not return it’s value as json

PublicationStatus

Create some system types

REFERENCE_CATEGORIES_RULE

Public Class Methods

__after_request() click to toggle source
# File pim.rb, line 1234
def self.__after_request

  # Pop the context of the calling request. Without a matching __before_request there is none, and only
  # the top frame is stopped.
  contexts = Thread.current[:__request_contexts]
  context = contexts && contexts.pop
  outermost = contexts.nil? || contexts.empty?
  Thread.current[:__request_contexts] = nil if contexts && contexts.empty?
  frame = context ? context[:frame] : ExecutionGuard.top

  # Stop the frame of this request and all the frames that interrupted scopes left above it, suspended
  # ones included, so no watchdog thread outlives the request. This comes before the exit blocks and
  # the cache cleanup, which must run even if the script's deadline elapses just now: the timeout is
  # raised afterwards.
  timeout = nil
  begin
    ExecutionGuard.drop_frame frame
  rescue ExecutionTimeoutError => e
    timeout = e
    retry if ExecutionGuard.active?(frame)
  end

  # Call exit blocks of this request
  request_exit_blocks = Thread.current[:__request_exit_blocks]
  unless request_exit_blocks.nil?

    request_exit_blocks.each do |block_name, block|
      begin
        block.call
      rescue => e
        PIM.log_error "Error executing request exit block '#{block_name}'", e
      end
    end

  end

  # Back to the request exit blocks and fiber local caches of the calling request (none for the outermost one)
  Thread.current[:__request_exit_blocks] = context && context[:exit_blocks]
  Thread.current[:__caches] = context && context[:caches]

  # The outermost request is over, so the next request on this thread decides again whether it is guarded
  Thread.current.thread_variable_set(DISABLE_EXECUTION_GUARD_THREAD_KEY, nil) if outermost

  raise timeout if timeout

end
__before_request(deadline_seconds: nil, deadline_reason_message: nil, script_path: nil, disable_execution_guard: false) click to toggle source

Starts a request on the current thread, to be ended by {__after_request}: pushes the context of the calling request, starts with fresh fiber-local caches and request exit blocks, and starts the frame with the deadline of the script (see {ExecutionGuard}).

With disable_execution_guard (server property RUBY_DISABLE_EXECUTION_GUARD) the request runs without execution guard: no frame, no watchdog, no deadline. The outermost request of a thread decides. Requests nested in it inherit that and ignore the option, so a request never mixes guarded and unguarded scopes, and a request that already runs is not affected when the property is changed meanwhile.

# File pim.rb, line 1184
def self.__before_request(deadline_seconds: nil, deadline_reason_message: nil, script_path: nil,
                          disable_execution_guard: false)

  # Push the context of the calling request, so __after_request can restore it. Requests can be nested (e.g.
  # a workflow calling before_store). The outermost one pushes the empty context, so whatever was left behind
  # outside of a request is cleared.
  contexts = (Thread.current[:__request_contexts] ||= [])
  outermost = contexts.empty?
  context = {
    caches: outermost ? nil : Thread.current[:__caches],
    exit_blocks: outermost ? nil : Thread.current[:__request_exit_blocks],
    frame: nil
  }
  contexts << context

  # The outermost request decides whether the execution guard is switched off, see above
  if outermost
    Thread.current.thread_variable_set(DISABLE_EXECUTION_GUARD_THREAD_KEY, disable_execution_guard ? true : nil)
  end

  # No request is active, so every execution-guard frame still on the stack was left behind by a request that
  # is over. Stop them, so the new request does not inherit their limits or get hit by their watchdogs. A
  # timeout that lands in here belongs to that earlier request.
  stale_frame = outermost ? ExecutionGuard.bottom : nil
  unless stale_frame.nil?
    PIM.log_warn "Dropping #{ExecutionGuard.depth} stale execution guard frame(s) left behind by an earlier request"
    begin
      ExecutionGuard.drop_frame stale_frame
    rescue ExecutionTimeoutError
      # Nothing to report for the new request
    end
  end

  # Start with fresh fiber local caches and request exit blocks
  Thread.current[:__caches] = nil
  Thread.current[:__request_exit_blocks] = {}

  # Without execution guard the request has no frame (stale ones of an earlier request were dropped above), so
  # __after_request has none to stop either
  return if execution_guard_disabled?

  # The frame is part of the context before it is started, so __after_request finds it however far the start got
  frame = ExecutionGuard.create_frame(deadline_seconds: deadline_seconds,
                                      reason_message: deadline_reason_message,
                                      script_path: script_path)
  context[:frame] = frame
  ExecutionGuard.start_frame frame

end
__fiber_local_cache(name) click to toggle source
# File pim.rb, line 676
def self.__fiber_local_cache name
  (Thread.current[:__caches] ||= {})[name.to_sym] ||= {}
end
active_module() click to toggle source
# File pim.rb, line 43
def self.active_module
  return @current_active_module if @current_active_module
  return PIM if all_modules.empty?
  @all_modules.values.last
end
additional_attributes_as_json(attribute_names_per_module) click to toggle source
# File pim.rb, line 230
def self.additional_attributes_as_json attribute_names_per_module
  return active_module.additional_attributes_as_json(attribute_names_per_module) if not all_modules.empty?
end
additional_categories_as_json(category_names_per_module) click to toggle source
# File pim.rb, line 226
def self.additional_categories_as_json category_names_per_module
  return active_module.additional_categories_as_json(category_names_per_module) if not all_modules.empty?
end
additional_section_attributes_as_json(layout_name, item) click to toggle source
# File pim.rb, line 234
def self.additional_section_attributes_as_json layout_name, item
  return active_module.additional_section_attributes_as_json(layout_name, item) if not all_modules.empty?
end
after_store(new_item, old_item = nil, opts = {}) click to toggle source
# File pim.rb, line 447
def self.after_store new_item, old_item = nil, opts = {}
  execute_item_method 'after_store', new_item, old_item, opts
end
all_modules() click to toggle source
# File pim.rb, line 35
def self.all_modules
  @all_modules ||= {}
end
at_request_exit(block_name = nil, &block) click to toggle source
# File pim.rb, line 1418
def self.at_request_exit block_name = nil, &block

  unless block.nil?

    block_name ||= block.to_s
    request_exit_blocks = (Thread.current[:__request_exit_blocks] ||= {})
    request_exit_blocks[block_name] = block
    return block_name

  end

  unless block_name.nil?
    request_exit_blocks = Thread.current[:__request_exit_blocks]
    return request_exit_blocks.nil? ? nil : request_exit_blocks[block_name]
  end

  return nil

end
attribute_rules_as_json() click to toggle source
# File pim.rb, line 170
def self.attribute_rules_as_json
  return active_module.attribute_rules_as_json if not all_modules.empty?
  return []
end
attributes_as_json() click to toggle source
# File pim.rb, line 129
def self.attributes_as_json
  return active_module.attributes_as_json if not all_modules.empty?
end
before_delete(new_item, opts = {}) click to toggle source
# File pim.rb, line 451
def self.before_delete new_item, opts = {}
  execute_item_method 'before_delete', new_item, nil, opts
end
before_store(old_item, new_item, called_from_import) click to toggle source

Old version: Just for compatibility with projects which directly called this method

# File pim.rb, line 407
def self.before_store old_item, new_item, called_from_import
  return [] if all_modules.empty?

  begin
    old_item = Item.from_json old_item, { :init_multivalue => false } if old_item
  rescue Exception => e
    PIM.log_warn("Could not resolve class of old item in 'before_store' (#{old_item})", e)
  end

  begin
    new_item = Item.from_json new_item, { :init_multivalue => false }
  rescue Exception => e
    PIM.log_warn("Could not resolve class of new item in 'before_store' (#{new_item})", e)
  end

  begin
    if new_item && new_item.kind_of?(Item)
      items_changed = [{ "ORIG" => new_item }]
      before_store = new_item.method(:before_store)
      if before_store.arity == 1 and not called_from_import
        items_changed_in_class = new_item.before_store(old_item)
      elsif before_store.arity == 2
        items_changed_in_class = new_item.before_store(old_item, called_from_import)
      end
      items_changed_in_class.each do |item|
        items_changed << { "SAVE" => item }
      end if items_changed_in_class and items_changed_in_class.respond_to? :each
      return items_changed
    end
  rescue Exception => e
    PIM.log_error("Error in 'before_store'", e)
  end

  return []
end
before_store_v2(new_item, old_item = nil, opts = {}) click to toggle source
# File pim.rb, line 443
def self.before_store_v2 new_item, old_item = nil, opts = {}
  execute_item_method 'before_store', new_item, old_item, opts
end
cache() click to toggle source
# File pim.rb, line 672
def self.cache
  PIM.__fiber_local_cache(:__pim_cache)
end
calculate_category(values) click to toggle source
# File pim.rb, line 660
def self.calculate_category values
  return nil if all_modules.empty?
  values = PIM::Utils.symbolized_hash(values)
  active_module.calculate_category values
end
calculate_primary_key(values) click to toggle source
# File pim.rb, line 666
def self.calculate_primary_key values
  return nil if all_modules.empty?
  values = PIM::Utils.symbolized_hash(values)
  active_module.calculate_primary_key values
end
call_item_method(method_name, new_item, old_item = nil, opts = {}) click to toggle source
# File pim.rb, line 563
def self.call_item_method method_name, new_item, old_item = nil, opts = {}

  method_sym = method_name.to_sym
  return nil if not new_item.respond_to?(method_sym)

  item_method = new_item.method(method_sym)
  arity = item_method.arity.abs
  result = nil

  #
  # Omit calling before_store if we are called in the context
  # of a depublication, and the current datamodel is not aware
  # that this can happen.
  #
  if method_name == 'before_store'
    called_from_depublication = opts[:called_from_depublication] || opts['called_from_depublication']
    if called_from_depublication and not active_module.has_option?(:consider_called_from_depublication)
      PIM.log_debug("Store method '#{method_name}' called from depublication, but current datamodel is not aware of this feature. Invocation will be omitted.")
      return nil
    end
  end

  if method_name == 'before_store' and not active_module.has_option?(:use_before_store_opts)
    # For compatibility: 'before_store' only has the 'called_from_import' parameter
    called_from_import = opts[:called_from_import] || opts['called_from_import']
    if arity == 2
      result = item_method.call(old_item, called_from_import)
    elsif arity == 1
      # For compatibility: Without 'called_from_import' parameter, 'before_store' is not called at all
      if not called_from_import
        result = item_method.call(old_item)
      end
    elsif arity == 0
      result = item_method.call()
    else
      PIM.log_warn("Store method '#{method_name}' does not have the correct number of arguments (#{arity} not in 0, 1 or 2)")
    end
  else
    if arity == 2
      result = item_method.call(old_item, opts)
    elsif arity == 1
      opts[:old_item] = opts['old_item'] = old_item
      result = item_method.call(opts)
    elsif arity == 0
      result = item_method.call()
    else
      PIM.log_warn("Item method '#{method_name}' does not have the correct number of arguments (#{arity} not in 0, 1 or 2)")
    end
  end

  return result

end
categories_as_json() click to toggle source
# File pim.rb, line 133
def self.categories_as_json
  return active_module.categories_as_json if not all_modules.empty?
end
category_as_json(category_name) click to toggle source
# File pim.rb, line 184
def self.category_as_json category_name
  return nil if all_modules.empty?
  category = active_module.category category_name
  return category.as_json if category
end
cleanup_items(items, include_empty) click to toggle source
# File pim.rb, line 549
def self.cleanup_items items, include_empty
  items.map do |item|
    if item.is_a?(PIM::Item)
      json_hash = item.as_json(include_empty: include_empty)
      json_string = json_hash.to_json # Creates a JSON string
      JSON.parse(json_string) # Creates a "cleaned up" JSON Hash
    elsif !is_java_item?(item) and item.is_a?(Hash)
      PIM.javaify(item)
    else
      item
    end
  end
end
communication_channels_as_json() click to toggle source
# File pim.rb, line 149
def self.communication_channels_as_json
  return active_module.communication_channels_as_json if not all_modules.empty?
end
communication_plans_as_json() click to toggle source
# File pim.rb, line 145
def self.communication_plans_as_json
  return active_module.communication_plans_as_json if not all_modules.empty?
end
compute_item_hierarchies_to_publish(primary_keys, publication = nil) click to toggle source

Should not be overridden in data model!

# File pim.rb, line 342
def self.compute_item_hierarchies_to_publish primary_keys, publication = nil

  return {} if active_module.nil?

  if active_module.respond_to?(:compute_item_hierarchies_to_publish)

    # Data model overrides 'compute_item_hierarchies_to_publish'
    PIM.log_info("Data model overrides 'compute_item_hierarchies_to_publish' method")
    return active_module.compute_item_hierarchies_to_publish(primary_keys)

  elsif active_module.respond_to?(:compute_items_to_publish)

    # Data model implements old 'compute_items_to_publish' method
    PIM.log_warn("Data model still implements old 'compute_items_to_publish' method")
    return active_module.compute_items_to_publish(primary_keys)

  else

    # Simple LRU item cache
    item_cache = PIM::LRUCache.new(20)

    # Options to retrieve publication hierarchies
    publication_type = PIM.get_value(publication, 'publication_type')

    publication_values = PIM.get_value(publication, 'publication_values')
    publish_only_valid = PIM.get_value(publication_values, 'only_valid')
    if publish_only_valid.nil?
      publish_only_valid = publication_type.nil? || publication_type == 'ADD'
    else
      publish_only_valid = publish_only_valid.to_s.downcase == 'true'
    end

    opts = {
      :item_cache => item_cache,
      :only_valid => publish_only_valid,
      :publication => (publication || {})
    }

    # Step one: Determine the top level items for the hierarchies to publish
    top_level_items = active_module.get_top_level_items(primary_keys, opts)

    # Step two: Determine the hierarchies for each top level item
    item_hierarchies = {}

    top_level_items.each do |top_level_item, status|

      # Set status from top_level_item result
      opts[:status] = status

      item_hierarchy = active_module.get_item_hierarchy(top_level_item, opts)
      item_hierarchy = active_module.update_hierarchy(item_hierarchy, opts)
      item_hierarchies[top_level_item] = item_hierarchy unless item_hierarchy.nil?

    end

    return item_hierarchies

  end
end
contact_attributes_as_json() click to toggle source
# File pim.rb, line 106
def self.contact_attributes_as_json
  return active_module.contact_attributes if not all_modules.empty?
  return []
end
copy_item_as_json(item, organization, template) click to toggle source
# File pim.rb, line 194
def self.copy_item_as_json item, organization, template
  return nil if all_modules.empty?
  template = PIM::Utils.hash_from_json template, "for copy item template"

  arity = active_module.method(:copy_item).arity.abs
  copy = case arity
    when 3 then active_module.copy_item item, organization, template
    else active_module.copy_item item, organization
  end
  return copy.as_json if copy
end
dashboard_attributes_as_json() click to toggle source
# File pim.rb, line 116
def self.dashboard_attributes_as_json
  return active_module.dashboard_attributes if not all_modules.empty?
  return []
end
data_model_as_json() click to toggle source
# File pim.rb, line 121
def self.data_model_as_json
  return active_module.data_model_as_json if not all_modules.empty?
end
data_model_services(service_data_model_hash_map = nil) click to toggle source
# File pim.rb, line 81
def self.data_model_services service_data_model_hash_map = nil
  @service_data_model_hash_map = service_data_model_hash_map unless service_data_model_hash_map.nil?
  @service_data_model_hash_map
end
data_model_values_as_json() click to toggle source
# File pim.rb, line 91
def self.data_model_values_as_json
  return active_module.data_model_values_as_json if not all_modules.empty?
  return {}
end
default_item_as_json(category_name, organization, user, template) click to toggle source
# File pim.rb, line 206
def self.default_item_as_json category_name, organization, user, template
  return nil if all_modules.empty?
  category = active_module.category category_name
  template = template.nil? ? {} : PIM::Utils.hash_from_json(template, "for default item template")

  if category and category.respond_to?(:default_item)
    organization = PIM::Services.__convert(organization, to: PIM::Services::OrganizationService::Organization)
    user = PIM::Services.__convert(user, to: PIM::Services::AccountService::LoginAccount)

    arity = category.method(:default_item).arity.abs
    item = case arity
      when 3 then category.default_item(organization, user, template)
      when 2 then category.default_item(organization, user)
      when 1 then category.default_item(organization)
      else category.default_item
    end
    return item.as_json if !item.nil?
  end
end
execute_item_method(method_name, new_item, old_item = nil, opts = {}) click to toggle source
# File pim.rb, line 455
def self.execute_item_method method_name, new_item, old_item = nil, opts = {}

  return {} if all_modules.empty?

  begin
    new_item = PIM::Item.from_json(new_item, :init_multivalue => false)
  rescue Exception => e
    PIM.log_warn("Could not create new item in '#{method_name}' for:\n#{new_item}", e)
  end
  return {} if not new_item.is_a?(PIM::Item)

  old_item ||= opts[:old_item]
  if old_item
    begin
      old_item = PIM::Item.from_json(old_item, :init_multivalue => false)
    rescue Exception => e
      PIM.log_warn("Could not create old item in '#{method_name}' for:\n#{old_item}", e)
    end
    old_item = nil if not old_item.is_a?(PIM::Item)
  end

  begin
    method_call_result = self.call_item_method(method_name, new_item, old_item, opts)
  rescue Exception => e
    PIM.log_error("Error calling '#{method_name}' method", e)
    return { "exception" => e.message }
  end

  result = {}

  include_empty = active_module.has_option?(:return_empty_values)

  if method_name == 'before_store'

    # Check if 'store_item' was set to false
    if PIM.get_value(method_call_result, :store_item) == false

      # NOTE: We could allow to set 'store_item' to a Hash in order to store the item as specified!
      # This way we would not merge the item with the old values at all!
      result['this'] = false
      result['call_after_store'] = false

    else

      result['this'] = new_item.as_json(include_empty: include_empty)

      call_after_store = PIM.get_value(method_call_result, :call_after_store)
      call_after_store = (self.call_item_method('call_after_store?', new_item, old_item, opts) == true) if call_after_store.nil?
      result['call_after_store'] = call_after_store

    end

  end

  if not is_empty?(method_call_result)
    if is_array?(method_call_result)
      result['others'] = self.cleanup_items(method_call_result, include_empty)
    elsif is_hash?(method_call_result)

      # Set 'status_values' from call result, if any
      self.set_javaify_method_call_result(result, method_call_result, :status_values)

      # Convert and add store_options and store_items to actual result
      result_others = {}

      # Use 'javaify' to convert the options to ensure no Ruby Symbol objects are used in Java
      self.set_javaify_method_call_result(result_others, method_call_result, :store_options)
      self.set_javaify_method_call_result(result_others, method_call_result, :delete_items)
      self.set_javaify_method_call_result(result_others, method_call_result, :validate_items)
      self.set_javaify_method_call_result(result_others, method_call_result, :validate_items_rules)
      self.set_javaify_method_call_result(result_others, method_call_result, :validate_items_attributes)

      # Set items to store as "cleaned up" JSON
      self.set_method_call_result(result_others, method_call_result, :store_items) { |items| PIM.cleanup_items(items, include_empty: include_empty) }

      result['others'] = result_others unless PIM.is_empty?(result_others)

    end
  end

  return result

end
execute_plan_action(action_type, action_creator, action_tags, action_date, channel, organization, data) click to toggle source
# File pim.rb, line 402
def self.execute_plan_action action_type, action_creator, action_tags, action_date, channel, organization, data
  return active_module.execute_plan_action(action_type, action_creator, action_tags, action_date, channel, organization, data) if not all_modules.empty?
end
execution_deadline() click to toggle source

Absolute deadline (Time) of the current execute frame, or nil if unlimited / no frame.

# File pim.rb, line 1413
def self.execution_deadline
  frame = ExecutionGuard.top
  frame.nil? ? nil : frame.execution_deadline
end
execution_guard(deadline_seconds: nil, reason_message: nil, script_path: nil) { || ... } click to toggle source

Run the block under an execution-guard frame of its own, with its watchdog. The limits of the frames it runs in (e.g. the script’s own deadline, see {__before_request}) keep applying to it, and deadline_seconds can add a wall-clock limit of its own (see {guard_execution_deadline} for a limit per part of the block). The frame and its watchdog are gone again when the block ends, however it ends, together with any frame that interrupted scopes left behind above it. Unlike {__before_request}, it does not reset fiber-local caches or request-exit blocks, so it is safe to nest for per-rule validation deadlines inside an active validate call. If the execution guard is switched off for the request (see {__before_request}) there is no frame and no watchdog: the block just runs. Returns the value of the block.

Future extension (not implemented): an optional create_exception callable could let a nested guard raise a distinct error type (e.g. for per-rule validation deadlines) while the default remains {ExecutionTimeoutError}. That would keep unstructured call budgets ({limit_execution_deadline} / RUBY_DEFAULT_EXECUTION_DEADLINE) complementary to structured per-rule caps without ambiguous rescue dispatch.

# File pim.rb, line 1298
def self.execution_guard deadline_seconds: nil, reason_message: nil, script_path: nil

  return yield if execution_guard_disabled?

  frame = ExecutionGuard.create_frame(deadline_seconds: deadline_seconds,
                                      reason_message: reason_message,
                                      script_path: script_path)
  begin
    ExecutionGuard.start_frame frame
    yield
  ensure
    ExecutionGuard.drop_frame frame
  end

end
execution_guard_disabled?() click to toggle source

Whether the execution guard is switched off for the request running on this thread. Then the request has no frame and no watchdog, {execution_guard} just runs its block, and the other guard methods find no frame and do nothing. Internal.

# File pim.rb, line 1171
def self.execution_guard_disabled?
  Thread.current.thread_variable_get(DISABLE_EXECUTION_GUARD_THREAD_KEY) == true
end
filters_as_json() click to toggle source
# File pim.rb, line 141
def self.filters_as_json
  return active_module.filters_as_json if not all_modules.empty?
end
format_deadline_text(seconds, nil_text: 'unlimited') click to toggle source

Format a deadline for messages: integral Floats as Integer, fractions unchanged. nil_text is used when seconds is nil (e.g. ‘unlimited’ or ‘unknown’).

# File pim.rb, line 707
def self.format_deadline_text seconds, nil_text: 'unlimited'
  return nil_text if seconds.nil?
  seconds == seconds.to_i ? seconds.to_i : seconds
end
guard_execution_deadline(seconds, reason_message = nil) { || ... } click to toggle source

Run the block with its own wall-clock budget of seconds on the top execution-guard frame, e.g. a nested frame of {execution_guard} to cap each validation rule. The budget replaces the frame’s current deadline (it may extend it, so use it on a nested frame, not on a script’s own frame) but never goes beyond the deadlines of the enclosing frames. It is cleared again when the block ends, even if the block raises.

A timeout cannot be dodged by the block: if one was delivered while the block ran (its own, or one of the enclosing frames) but the block ended normally anyway, the code in it swallowed the error (rescue => e), so the error is raised when the block ends. That does not bound a block which never ends.

Returns the value of the block. Without a budget (no execution guard is active, or seconds is nil or not positive) the block just runs; only the check for swallowed timeouts of the enclosing frames is still done.

# File pim.rb, line 1387
def self.guard_execution_deadline seconds, reason_message = nil

  frame = ExecutionGuard.top
  return yield if frame.nil?

  limited = seconds.to_f > 0.0
  frame.reset_execution_deadline(seconds, reason_message) if limited

  result = nil
  delivered = nil

  begin
    result = yield
  ensure
    delivered = limited ? frame.clear_execution_deadline : frame.delivered_timeout_error
  end

  # Only reached if the block ended without an exception
  raise delivered if delivered

  result

end
has_module?(module_name) click to toggle source
# File pim.rb, line 39
def self.has_module? module_name
  @all_modules && @all_modules.include?(module_name.to_s)
end
hierarchies_as_json() click to toggle source
# File pim.rb, line 153
def self.hierarchies_as_json
  return as_json(active_module.all_hierarchies) if not all_modules.empty?
end
in_platform_publication_as_json() click to toggle source
# File pim.rb, line 157
def self.in_platform_publication_as_json
  return as_json(active_module.in_platform_publication) if not all_modules.empty?
end
item_hierarchy_graph(item, hierarchy_name = nil) click to toggle source

Return an item hierarchy graph for the specified item and hierarchy name. If hiearchy_name is nil, returns the “default” hierarchy.

The resulted hierarchy should have the following JSON structure:
 {
   "items": [
       {
           "primaryKey": "22222",    <- Primary key of the item in the hierarchy
           "level": 1                <- Level of hierarchy, starts with 0
       },
       ...
   ], // Alternatively, it is also possible to use primary keys only,
        e.g. items: [ "1234", "4567", ... ] however no mixture of both!
   "relations": [
       {
           "parentItem": "",         <- Primary key of the parent item
           "childItem": "",          <- Primary key of the child item
           "relationValue": "20x",   <- Value of the relationship (optional)
           "relationAttribute": "amount" <- The unit of the relationship (optional)
       },
       ...
   ]

}

# File pim.rb, line 286
def self.item_hierarchy_graph item, hierarchy_name = nil

  return nil if all_modules.empty?

  if is_hash?(item)
    begin
      item = Item.from_json(item, { :init_multivalue => false })
    rescue Exception => e
      PIM.log_warn("Could not create item in 'item_hierarchy_graph' for:\n#{item}", e)
    end
  elsif is_string?(item)
    primary_key = item
    item = PIM::Services::ItemService.get_item_as_ruby(primary_key, :init_multivalue => false)
    PIM.log_warn("Item for primary key '#{primary_key}' in item_hierarchy_graph not found") if item.nil?
  end
  return nil if not item.is_a?(Item)

  # Get hierarchy graph for item
  hierarchy_graph = item.method(:hierarchy_graph)
  if hierarchy_graph
    arity = hierarchy_graph.arity
    hierarchy = case arity
      when 0 then item.hierarchy_graph()
      when 1, -1 then item.hierarchy_graph(hierarchy_name)
      when 2, -2 then item.hierarchy_graph(hierarchy_name, {})
      else
        PIM.log_warn("'hierarchy_graph' method for category #{item.class} does not have the correct number of arguments (#{arity.abs} not in 0, 1 or 2)")
        nil
    end
  else
    PIM.log_info("No 'hierarchy_graph' method in category #{item.class}")
    hierarchy = nil
  end

  if is_hash?(hierarchy)
    hierarchy[:items].map! { |item| camelize(item) } if hierarchy[:items]
    hierarchy[:relations].map! { |relation| camelize(relation) } if hierarchy[:relations]
  end

  return hierarchy

end
item_scopes_as_json() click to toggle source
# File pim.rb, line 161
def self.item_scopes_as_json
  return as_json(active_module.item_scopes) if not all_modules.empty?
end
layout_as_json(layout_name, opts = {}) click to toggle source
# File pim.rb, line 249
def self.layout_as_json layout_name, opts = {}
  return nil if all_modules.empty?
  layout = active_module.layout layout_name
  return layout.as_json(opts) if layout
end
layouts_as_json() click to toggle source
# File pim.rb, line 137
def self.layouts_as_json
  return active_module.layouts_as_json if not all_modules.empty?
end
limit_execution_deadline(seconds, reason_message = nil) click to toggle source

Tighten the current execute’s remaining wall-clock budget to at most seconds. Never extends an existing deadline. Raises ExecutionTimeoutError if already past. Optional reason_message replaces the frame’s timeout explanation when provided. Does nothing when no execution guard is active.

# File pim.rb, line 1367
def self.limit_execution_deadline seconds, reason_message = nil
  frame = ExecutionGuard.top
  frame.limit_execution_deadline(seconds, reason_message) unless frame.nil?
end
managing_user_roles_as_json(user, contact) click to toggle source
# File pim.rb, line 242
def self.managing_user_roles_as_json user, contact
  # FIXME: Add 'Contact' class in 'ContactService' to support execution on external Ruby!
  #    user = PIM::Services.__convert(user, to: PIM::Services::AccountService::LoginAccount)
  #    contact = PIM::Services.__convert(contact, to: PIM::Services::ContactService::Contact)
  return active_module.managing_user_roles_as_json(user, contact) if not all_modules.empty?
end
mapping_functions_as_json() click to toggle source
# File pim.rb, line 190
def self.mapping_functions_as_json
  return active_module.mapping_functions_as_json if not all_modules.empty?
end
media_asset_naming_patterns_as_json() click to toggle source
# File pim.rb, line 175
def self.media_asset_naming_patterns_as_json
  return as_json(active_module.media_asset_naming_patterns)
end
merge_item_reviews(new_item_review, old_item_review) click to toggle source
# File pim.rb, line 329
def self.merge_item_reviews new_item_review, old_item_review

  new_item_review = PIM::Services.__convert(new_item_review, to: PIM::Services::ReviewService::ItemReview, force: true)
  old_item_review = PIM::Services.__convert(old_item_review, to: PIM::Services::ReviewService::ItemReview, force: true)

  result = active_module.merge_item_reviews(new_item_review, old_item_review)
  return result if result.nil? || result == true || result == false

  return PIM.javaify(PIM::Utils.as_json(result))

end
migrate_as_json(args) click to toggle source
# File pim.rb, line 255
def self.migrate_as_json args
  return nil if all_modules.empty?
  migrated_values = active_module.migrate!(args)
  return as_json(migrated_values)
end
option_lists_as_json() click to toggle source
# File pim.rb, line 125
def self.option_lists_as_json
  return active_module.option_lists_as_json if not all_modules.empty?
end
organization_attributes_as_json() click to toggle source
# File pim.rb, line 96
def self.organization_attributes_as_json
  return active_module.organization_attributes if not all_modules.empty?
  return []
end
print_json(object) click to toggle source
reset() click to toggle source
# File pim.rb, line 49
def self.reset
  all_modules.keys.each do |m|
    PIM::Utils.remove_const_on_parent m
  end
  @all_modules = nil
end
reset_active_module() click to toggle source
# File pim.rb, line 56
def self.reset_active_module
  return if all_modules.empty?
  active_module_key = active_module.to_s
  PIM::Utils.remove_const_on_parent(active_module_key)
  new_all_modules = @all_modules.dup
  new_all_modules.delete(active_module_key)
  @all_modules = new_all_modules
end
return_json(object) click to toggle source
# File pim.rb, line 70
def self.return_json object
  return object.is_a?(String)? object : as_json(object).to_json if object
end
review_attributes_as_json() click to toggle source
# File pim.rb, line 111
def self.review_attributes_as_json
  return active_module.review_attributes if not all_modules.empty?
  return []
end
roles_as_json() click to toggle source
# File pim.rb, line 86
def self.roles_as_json
  return active_module.roles_as_json if not all_modules.empty?
  return PIM.all_roles_as_json
end
set_javaify_method_call_result(result, method_call_result, key) click to toggle source
# File pim.rb, line 545
def self.set_javaify_method_call_result result, method_call_result, key
  self.set_method_call_result(result, method_call_result, key) { |v| PIM.javaify(v) }
end
set_method_call_result(result, method_call_result, key, &convert_block) click to toggle source
# File pim.rb, line 539
def self.set_method_call_result result, method_call_result, key, &convert_block
  value = PIM.get_value(method_call_result, key)
  value = convert_block.call(value) unless value.nil?
  result[key.to_s] = value unless PIM.is_empty?(value)
end
suspend_execution_guard() { || ... } click to toggle source

Suspend watchdog Thread#raise on every frame of the current thread for block. Wall-clock still advances (service calls count toward the deadline). After the block, {ExecutionGuard::Frame#resume} raises {ExecutionTimeoutError} if the top frame’s deadline elapsed — safe once Yakut Fiber.yield has completed. No-op (just yields) when no guard is active.

Note: Thread.handle_interrupt is not used. On JRuby its mask is local to a Fiber, so it would not cover the wait of the thread outside of the Fiber while a Yakut callback has yielded. The suspend count belongs to the frame, and the watchdog checks it and raises under the same mutex hold, so it covers the whole call.

# File pim.rb, line 1325
def self.suspend_execution_guard

  frames = ExecutionGuard.stack.dup
  return yield if frames.empty?

  # The suspensions of every frame are restored to what they were, whatever got done when a raise lands
  # while the frames are being suspended: an enclosing call keeps its own suspension, and no frame stays
  # suspended and is never enforced again.
  counts = frames.map { |frame| frame.suspend_count }

  begin

    frames.each { |frame| frame.suspend }
    yield

  ensure

    # Resume every frame even if a timeout is raised mid-loop. Bottom-up, so the top frame, which
    # also delivers the limits of the frames below it, resumes last.
    resume_error = nil

    frames.each_with_index do |frame, index|
      while frame.suspend_count > counts[index]
        begin
          frame.resume
        rescue => e
          resume_error ||= e
        end
      end
    end

    raise resume_error if resume_error

  end

end
task_tags_as_json() click to toggle source
# File pim.rb, line 165
def self.task_tags_as_json
  return as_json(active_module.task_tags) if not all_modules.empty?
  return []
end
to_json(object) click to toggle source
# File pim.rb, line 74
def self.to_json object
  return nil if object.nil?
  return object if object.is_a?(String)
  return object.to_json if object.respond_to?(:to_json)
  return object.to_s
end
user_attributes_as_json() click to toggle source
# File pim.rb, line 101
def self.user_attributes_as_json
  return active_module.user_attributes if not all_modules.empty?
  return []
end
user_data_model_as_json(user) click to toggle source
# File pim.rb, line 238
def self.user_data_model_as_json user
  return active_module.user_data_model_as_json(user) if not all_modules.empty?
end
validate_as_json(old_item, new_item, attributes = nil) click to toggle source
# File pim.rb, line 617
def self.validate_as_json old_item, new_item, attributes = nil
  return nil if all_modules.empty?

  old_item = PIM::Utils.symbolized_hash(old_item)
  new_item = PIM::Utils.symbolized_hash(new_item)
  attributes = PIM::Utils.symbolized_array(attributes)

  validation_result = active_module.validate old_item, new_item, *attributes
  validation_result.as_json

end
validate_item_as_json(item, opts = {}) click to toggle source
# File pim.rb, line 629
def self.validate_item_as_json item, opts = {}
  return nil if all_modules.empty?

  item = PIM::Utils.symbolized_hash(item)
  opts = PIM::Utils.symbolized_hash(opts)

  old_item = PIM::Utils.symbolized_hash(opts[:old_item])
  revalidate_attributes = PIM::Utils.symbolized_array(opts[:revalidate_attributes])

  calculate_attribute_states_only = PIM.get_value(opts, :calculate_attribute_states_only)
  validation_rule_default_execution_deadline = PIM.get_value(opts, :validation_rule_default_execution_deadline)
  validation_rule_max_execution_deadline = PIM.get_value(opts, :validation_rule_max_execution_deadline)

  validation_result = active_module.validate_item item,
                                                  old_item: old_item,
                                                  revalidate_attributes: revalidate_attributes,
                                                  calculate_attribute_states_only: calculate_attribute_states_only,
                                                  validation_rule_default_execution_deadline:
                                                    validation_rule_default_execution_deadline,
                                                  validation_rule_max_execution_deadline:
                                                    validation_rule_max_execution_deadline

  validation_result.as_json

end
validate_items_as_json(items, opts = {}) click to toggle source
# File pim.rb, line 655
def self.validate_items_as_json items, opts = {}
  return nil if all_modules.empty?
  return as_json(active_module.validate_items(*items, **opts))
end

Protected Class Methods

execute_in_module(execute_module, &block) click to toggle source
# File pim.rb, line 1440
def self.execute_in_module execute_module, &block
  old_active_module = @current_active_module
  begin
    execute_module = get_module(execute_module)
    return nil if !execute_module
    @current_active_module = execute_module
    return execute_module.module_exec(&block)
  ensure
    @current_active_module = old_active_module
  end
end