lib/rcs-db/link_manager.rb
#
# Module for handling links between entities
#
# from RCS::Common
require 'rcs-common/trace'
module RCS
module DB
class LinkManager
include Singleton
include Tracer
def add_link(params)
first_entity = params[:from]
second_entity = params[:to]
raise "Cannot create link on itself (#{first_entity.name})" unless first_entity != second_entity
if params[:versus]
versus = params[:versus].to_sym
opposite_versus = versus if versus.eql? :both
opposite_versus ||= (versus.eql? :in) ? :out : :in
end
# default is automatic
params[:level] ||= :automatic
trace :info, "Creating link between #{first_entity.name.inspect} and #{second_entity.name.inspect} [#{params[:level]}, #{params[:type]}, #{versus}]"
# create a link in this entity
first_link = first_entity.links.find_or_initialize_by(le: second_entity._id)
first_link.first_seen = Time.now.getutc.to_i unless first_link.first_seen
first_link.last_seen = Time.now.getutc.to_i
first_link.set_level(params[:level])
first_link.set_type(params[:type])
first_link.set_versus(versus) if versus
first_link.add_info params[:info] if params[:info]
first_link.rel = params[:rel] if params[:rel]
# and also create the reverse in the other entity
second_link = second_entity.links.find_or_initialize_by(le: first_entity._id)
second_link.first_seen = Time.now.getutc.to_i unless second_link.first_seen
second_link.last_seen = Time.now.getutc.to_i
second_link.set_level(params[:level])
second_link.set_type(params[:type])
second_link.set_versus(opposite_versus) if opposite_versus
second_link.add_info params[:info] if params[:info]
second_link.rel = params[:rel] if params[:rel]
new_links = first_link.new_record? && second_link.new_record?
first_link.save
second_link.save
if new_links
# check if :ghosts have to be promoted to :automatic
first_entity.promote_ghost
second_entity.promote_ghost
alert_new_link [first_entity, second_entity]
end
[[first_entity, first_link], [second_entity, second_link]].each do |entity, link|
# Do not send any notify if the link hasn't changed
next if link.previous_changes.empty?
# notify the links
entity.push_modify_entity
end
if first_link.cross_operation?
Entity.create_or_update_operation_group(first_entity.path[0], second_entity.path[0])
end
# TODO: update any groups even where a link is destroyed
return first_link
end
def alert_new_link(entities)
return if entities.first.level == :ghost
return if entities.last.level == :ghost
RCS::DB::Alerting.new_link(entities)
end
def edit_link(params)
first_entity = params[:from]
second_entity = params[:to]
if params[:versus]
versus = params[:versus].to_sym
opposite_versus = versus if versus.eql? :both
opposite_versus ||= (versus.eql? :in) ? :out : :in
end
first_link = first_entity.links.connected_to(second_entity).first
first_link.set_level(params[:level]) if params[:level]
first_link.type = params[:type] if params[:type]
first_link.versus = versus if versus
first_link.add_info params[:info] if params[:info]
first_link.rel = params[:rel] if params[:rel]
first_link.save
second_link = second_entity.links.connected_to(first_entity).first
second_link.set_level(params[:level]) if params[:level]
second_link.type = params[:type] if params[:type]
second_link.versus = opposite_versus if opposite_versus
second_link.add_info params[:info] if params[:info]
second_link.rel = params[:rel] if params[:rel]
second_link.save
# notify the links
first_entity.push_modify_entity
second_entity.push_modify_entity
return first_link
end
def del_link(params)
first_entity = params[:from]
second_entity = params[:to]
trace :info, "Deleting links between '#{first_entity.name}' and '#{second_entity.name}'"
destroyed = first_entity.links.connected_to(second_entity).destroy_all
destroyed += second_entity.links.connected_to(first_entity).destroy_all
# notify the links
if destroyed > 0
first_entity.push_modify_entity
second_entity.push_modify_entity
end
nil
end
def del_all_links(entity)
trace :info, "Deleting all links attached to '#{entity.name}'"
connected_entities = entity
.links
.map { |link| link.linked_entity }
.compact
.uniq
connected_entities.each do |connected_entity|
if connected_entity.links.connected_to(entity).destroy_all > 0
connected_entity.push_modify_entity
end
end
if entity.links.destroy_all > 0
entity.push_modify_entity
end
nil
end
def move_links(params)
first_entity = params[:from]
second_entity = params[:to]
trace(:info, "Moving links from '#{first_entity.name}' to '#{second_entity.name}'")
links_to_create = []
links_to_delete = []
first_entity.links.each do |link|
linked_entity = link.linked_entity
# Keep intact links between the twp
next if second_entity == linked_entity
# Delete link on the first entity
links_to_delete << {from: first_entity, to: linked_entity}
# Add/update link on the second entity
new_link_attributes = link.attributes.reject { |name| %w[le _id].include?(name) }.symbolize_keys
new_link_attributes.merge!(from: second_entity, to: linked_entity)
existing_link = second_entity.links.connected_to(linked_entity).first
# Resolve link conflict
if existing_link
new_link_attributes.merge!(level: :automatic) if [existing_link.level, link.level].include?(:manual)
new_link_attributes.merge!(rel: [existing_link.rel, link.rel].max)
end
links_to_create << new_link_attributes
end
links_to_delete.each { |params| del_link(params) }
links_to_create.each { |params| add_link(params) }
nil
end
# Check if two entities are the same and create a link between them.
# Search for other entities with the same handle, if found we consider them identical.
def check_identity(entity, handle)
Entity.with_handle(handle.type, handle.handle, exclude: entity).each do |other_entity|
trace :info, "Identity match: #{entity.name.inspect} and #{other_entity.name.inspect} -> #{handle.handle.inspect}"
# Create the (identity) link
add_link(from: entity, to: other_entity, type: :identity, info: handle.handle, versus: :both)
end
end
# Creates a link from ENTITY to any onthe entity that have communicated with HANDLE (based on aggregates)
def link_handle(entity, handle)
HandleBook.entities_that_communicate_with(handle.type, handle.handle, exclude: entity).each do |peer_entity|
trace :debug, "Entity #{entity.name.inspect} must be linked to #{peer_entity.name.inspect} via #{handle.handle.inspect} (#{handle.type.inspect})"
versus = Aggregate.target(peer_entity.target_id).versus_of_communications_with(handle)
if versus
add_link(from: peer_entity, to: entity, type: :peer, level: :automatic, info: handle.handle, versus: versus)
else
trace :warn, "Cannot tell the communication versus"
end
end
end
end
end
end