module RCAP module Base class Alert include Validation STATUS_ACTUAL = "Actual" STATUS_EXERCISE = "Exercise" STATUS_SYSTEM = "System" STATUS_TEST = "Test" # Valid values for status VALID_STATUSES = [ STATUS_ACTUAL, STATUS_EXERCISE, STATUS_SYSTEM, STATUS_TEST ] MSG_TYPE_ALERT = "Alert" MSG_TYPE_UPDATE = "Update" MSG_TYPE_CANCEL = "Cancel" MSG_TYPE_ACK = "Ack" MSG_TYPE_ERROR = "Error" # Valid values for msg_type VALID_MSG_TYPES = [ MSG_TYPE_ALERT, MSG_TYPE_UPDATE, MSG_TYPE_CANCEL, MSG_TYPE_ACK, MSG_TYPE_ERROR ] SCOPE_PUBLIC = "Public" SCOPE_RESTRICTED = "Restricted" SCOPE_PRIVATE = "Private" # Valid values for scope VALID_SCOPES = [ SCOPE_PUBLIC, SCOPE_PRIVATE, SCOPE_RESTRICTED ] # @return [String] If not set a UUID will be set by default on object initialisation attr_accessor( :identifier) # @return [String] attr_accessor( :sender ) # @return [DateTime] If not set will be time of creation. attr_accessor( :sent ) # @return [String] Can only be one of {VALID_STATUSES} attr_accessor( :status ) # @return [String] Can only be one of {VALID_MSG_TYPES} attr_accessor( :msg_type ) # @return [String] Can only be one of {VALID_SCOPES} attr_accessor( :scope ) # @return [String] attr_accessor( :source ) # @return [String ] Required if scope is {SCOPE_RESTRICTED} attr_accessor( :restriction ) # @return [String] attr_accessor( :note ) # @return [Array] Collection of address strings. Depends on scope being {SCOPE_PRIVATE} attr_reader( :addresses ) # @return [Array] attr_reader( :codes ) # @return [Array] Collection of references to previous alerts # @see #to_reference attr_reader( :references) # @return [Array] Collection of incident strings attr_reader( :incidents ) # @return [Array] attr_reader( :infos ) validates_presence_of( :identifier, :sender, :sent, :status, :msg_type, :scope ) validates_inclusion_of( :status, :in => VALID_STATUSES ) validates_inclusion_of( :msg_type, :in => VALID_MSG_TYPES ) validates_inclusion_of( :scope, :in => VALID_SCOPES ) validates_format_of( :identifier, :with => ALLOWED_CHARACTERS ) validates_format_of( :sender , :with => ALLOWED_CHARACTERS ) validates_conditional_presence_of( :addresses, :when => :scope, :is => SCOPE_PRIVATE ) validates_conditional_presence_of( :restriction, :when => :scope, :is => SCOPE_RESTRICTED ) validates_collection_of( :infos ) # Initialises a new Alert object. Yields the initialised alert to a block. # # @example # alert = RCAP::CAP_1_2::Alert.new do |alert| # alert.sender = alerts@example.org # alert.status = Alert::STATUS_ACTUAL # alert.msg_type = Alert::MSG_TYPE_ALERT # alert.scope = Alert::SCOPE_PUBLIC # end # # @yieldparam alert [Alert] The newly initialised Alert. def initialize @identifier = RCAP.generate_identifier @addresses = [] @codes = [] @references = [] @incidents = [] @infos = [] yield( self ) if block_given? end # Creates a new {Info} object and adds it to the {#infos} array. # # @see Info#initialize # @yield [Info] The newly initialised Info object. # @return [Info] The initialised Info object after being yielded to the block def add_info self.info_class.new.tap do |info| yield( info ) if block_given? @infos << info end end XML_ELEMENT_NAME = 'alert' IDENTIFIER_ELEMENT_NAME = 'identifier' SENDER_ELEMENT_NAME = 'sender' SENT_ELEMENT_NAME = 'sent' STATUS_ELEMENT_NAME = 'status' MSG_TYPE_ELEMENT_NAME = 'msgType' SOURCE_ELEMENT_NAME = 'source' SCOPE_ELEMENT_NAME = 'scope' RESTRICTION_ELEMENT_NAME = 'restriction' ADDRESSES_ELEMENT_NAME = 'addresses' CODE_ELEMENT_NAME = 'code' NOTE_ELEMENT_NAME = 'note' REFERENCES_ELEMENT_NAME = 'references' INCIDENTS_ELEMENT_NAME = 'incidents' # @return [REXML::Element] def to_xml_element xml_element = REXML::Element.new( XML_ELEMENT_NAME ) xml_element.add_namespace( self.class::XMLNS ) xml_element.add_element( IDENTIFIER_ELEMENT_NAME ).add_text( @identifier ) if @identifier xml_element.add_element( SENDER_ELEMENT_NAME ).add_text( @sender ) if @sender xml_element.add_element( SENT_ELEMENT_NAME ).add_text( @sent.to_s_for_cap ) if @sent xml_element.add_element( STATUS_ELEMENT_NAME ).add_text( @status ) if @status xml_element.add_element( MSG_TYPE_ELEMENT_NAME ).add_text( @msg_type ) if @msg_type xml_element.add_element( SOURCE_ELEMENT_NAME ).add_text( @source ) if @source xml_element.add_element( SCOPE_ELEMENT_NAME ).add_text( @scope ) if @scope xml_element.add_element( RESTRICTION_ELEMENT_NAME ).add_text( @restriction ) if @restriction if @addresses.any? xml_element.add_element( ADDRESSES_ELEMENT_NAME ).add_text( @addresses.to_s_for_cap ) end @codes.each do |code| xml_element.add_element( CODE_ELEMENT_NAME ).add_text( code ) end xml_element.add_element( NOTE_ELEMENT_NAME ).add_text( @note ) if @note if @references.any? xml_element.add_element( REFERENCES_ELEMENT_NAME ).add_text( @references.join( ' ' )) end if @incidents.any? xml_element.add_element( INCIDENTS_ELEMENT_NAME ).add_text( @incidents.join( ' ' )) end @infos.each do |info| xml_element.add_element( info.to_xml_element ) end xml_element end # @return [REXML::Document] def to_xml_document xml_document = REXML::Document.new xml_document.add( REXML::XMLDecl.new ) xml_document.add( self.to_xml_element ) xml_document end # Returns a string containing the XML representation of the alert. # # @param [true,false] pretty_print Pretty print output # @return [String] def to_xml( pretty_print = false ) if pretty_print xml_document = "" RCAP::XML_PRETTY_PRINTER.write( self.to_xml_document, xml_document ) xml_document else self.to_xml_document.to_s end end # Returns a string representation of the alert suitable for usage as a reference in a CAP message of the form # sender,identifier,sent # # @return [String] def to_reference "#{ @sender },#{ @identifier },#{ RCAP.to_s_for_cap( @sent )}" end # @return [String] def inspect alert_inspect = [ "CAP Version: #{ self.class::CAP_VERSION }", "Identifier: #{ @identifier }", "Sender: #{ @sender }", "Sent: #{ @sent }", "Status: #{ @status }", "Message Type: #{ @msg_type }", "Source: #{ @source }", "Scope: #{ @scope }", "Restriction: #{ @restriction }", "Addresses: #{ @addresses.to_s_for_cap }", "Codes:", @codes.map{ |code| " " + code }.join("\n")+"", "Note: #{ @note }", "References: #{ @references.join( ' ' )}", "Incidents: #{ @incidents.join( ' ')}", "Information:", @infos.map{ |info| " " + info.to_s }.join( "\n" )].join( "\n" ) RCAP.format_lines_for_inspect( 'ALERT', alert_inspect ) end # Returns a string representation of the alert of the form # sender/identifier/sent # See {#to_reference} for another string representation suitable as a CAP reference. # # @return [String] def to_s "#{ @sender }/#{ @identifier }/#{ RCAP.to_s_for_cap( @sent )}" end XPATH = 'cap:alert' IDENTIFIER_XPATH = "cap:#{ IDENTIFIER_ELEMENT_NAME }" SENDER_XPATH = "cap:#{ SENDER_ELEMENT_NAME }" SENT_XPATH = "cap:#{ SENT_ELEMENT_NAME }" STATUS_XPATH = "cap:#{ STATUS_ELEMENT_NAME }" MSG_TYPE_XPATH = "cap:#{ MSG_TYPE_ELEMENT_NAME }" SOURCE_XPATH = "cap:#{ SOURCE_ELEMENT_NAME }" SCOPE_XPATH = "cap:#{ SCOPE_ELEMENT_NAME }" RESTRICTION_XPATH = "cap:#{ RESTRICTION_ELEMENT_NAME }" ADDRESSES_XPATH = "cap:#{ ADDRESSES_ELEMENT_NAME }" CODE_XPATH = "cap:#{ CODE_ELEMENT_NAME }" NOTE_XPATH = "cap:#{ NOTE_ELEMENT_NAME }" REFERENCES_XPATH = "cap:#{ REFERENCES_ELEMENT_NAME }" INCIDENTS_XPATH = "cap:#{ INCIDENTS_ELEMENT_NAME }" # @param [REXML::Element] alert_xml_element # @return [RCAP::CAP_1_0::Alert] def self.from_xml_element( alert_xml_element ) self.new do |alert| alert.identifier = RCAP.xpath_text( alert_xml_element, IDENTIFIER_XPATH, alert.xmlns ) alert.sender = RCAP.xpath_text( alert_xml_element, SENDER_XPATH, alert.xmlns ) alert.sent = RCAP.parse_datetime( RCAP.xpath_text( alert_xml_element, SENT_XPATH, alert.xmlns )) alert.status = RCAP.xpath_text( alert_xml_element, STATUS_XPATH, alert.xmlns ) alert.msg_type = RCAP.xpath_text( alert_xml_element, MSG_TYPE_XPATH, alert.xmlns ) alert.source = RCAP.xpath_text( alert_xml_element, SOURCE_XPATH, alert.xmlns ) alert.scope = RCAP.xpath_text( alert_xml_element, SCOPE_XPATH, alert.xmlns ) alert.restriction = RCAP.xpath_text( alert_xml_element, RESTRICTION_XPATH, alert.xmlns ) RCAP.unpack_if_given( RCAP.xpath_text( alert_xml_element, ADDRESSES_XPATH, alert.xmlns )).each do |address| alert.addresses << address.strip end RCAP.xpath_match( alert_xml_element, CODE_XPATH, alert.xmlns ).each do |element| alert.codes << element.text.strip end alert.note = RCAP.xpath_text( alert_xml_element, NOTE_XPATH, alert.xmlns ) RCAP.unpack_if_given( RCAP.xpath_text( alert_xml_element, REFERENCES_XPATH, alert.xmlns )).each do |reference| alert.references << reference.strip end RCAP.unpack_if_given( RCAP.xpath_text( alert_xml_element, INCIDENTS_XPATH, alert.xmlns )).each do |incident| alert.incidents << incident.strip end RCAP.xpath_match( alert_xml_element, Info::XPATH, alert.xmlns ).each do |element| alert.infos << alert.info_class.from_xml_element( element ) end end end # @param [REXML::Document] xml_document # @return [Alert] def self.from_xml_document( xml_document ) self.from_xml_element( xml_document.root ) end # Initialise an Alert object from an XML string. Any object that is a subclass of IO (e.g. File) can be passed in. # # @param [String] xml # @return [Alert] def self.from_xml( xml ) self.from_xml_document( REXML::Document.new( xml )) end CAP_VERSION_YAML = "CAP Version" IDENTIFIER_YAML = "Identifier" SENDER_YAML = "Sender" SENT_YAML = "Sent" STATUS_YAML = "Status" MSG_TYPE_YAML = "Message Type" SOURCE_YAML = "Source" SCOPE_YAML = "Scope" RESTRICTION_YAML = "Restriction" ADDRESSES_YAML = "Addresses" CODES_YAML = "Codes" NOTE_YAML = "Note" REFERENCES_YAML = "References" INCIDENTS_YAML = "Incidents" INFOS_YAML = "Information" # Returns a string containing the YAML representation of the alert. # # @return [String] def to_yaml( options = {} ) RCAP.attribute_values_to_hash( [ CAP_VERSION_YAML, self.class::CAP_VERSION ], [ IDENTIFIER_YAML, @identifier ], [ SENDER_YAML, @sender ], [ SENT_YAML, @sent ], [ STATUS_YAML, @status ], [ MSG_TYPE_YAML, @msg_type ], [ SOURCE_YAML, @source ], [ SCOPE_YAML, @scope ], [ RESTRICTION_YAML, @restriction ], [ ADDRESSES_YAML, @addresses ], [ CODES_YAML, @codes ], [ NOTE_YAML, @note ], [ REFERENCES_YAML, @references ], [ INCIDENTS_YAML, @incidents ], [ INFOS_YAML, @infos.map( &:to_yaml_data )]).to_yaml( options ) end # Initialise an Alert object from a YAML string. Any object that is a subclass of IO (e.g. File) can be passed in. # # @param [String] yaml # @return [Alert] def self.from_yaml( yaml ) self.from_yaml_data( YAML.load( yaml )) end # Initialise an Alert object from a hash reutrned from YAML.load. # # @param [hash] alert_yaml_data # @return [Alert] def self.from_yaml_data( alert_yaml_data ) self.new do |alert| alert.identifier = RCAP.strip_if_given( alert_yaml_data[ IDENTIFIER_YAML ]) alert.sender = RCAP.strip_if_given( alert_yaml_data[ SENDER_YAML ]) alert.sent = RCAP.parse_datetime( alert_yaml_data[ SENT_YAML ]) alert.status = RCAP.strip_if_given( alert_yaml_data[ STATUS_YAML ]) alert.msg_type = RCAP.strip_if_given( alert_yaml_data[ MSG_TYPE_YAML ]) alert.source = RCAP.strip_if_given( alert_yaml_data[ SOURCE_YAML ]) alert.scope = RCAP.strip_if_given( alert_yaml_data[ SCOPE_YAML ]) alert.restriction = RCAP.strip_if_given( alert_yaml_data[ RESTRICTION_YAML ]) Array( alert_yaml_data[ ADDRESSES_YAML ]).each do |address| alert.addresses << address.strip end Array( alert_yaml_data[ CODES_YAML ]).each do |code| alert.codes << code.strip end alert.note = alert_yaml_data[ NOTE_YAML ] Array( alert_yaml_data[ REFERENCES_YAML ]).each do |reference| alert.references << reference.strip end Array( alert_yaml_data[ INCIDENTS_YAML ]).each do |incident| alert.incidents << incident.strip end Array( alert_yaml_data[ INFOS_YAML ]).each do |info_yaml_data| alert.infos << alert.info_class.from_yaml_data( info_yaml_data ) end end end CAP_VERSION_KEY = 'cap_version' IDENTIFIER_KEY = 'identifier' SENDER_KEY = 'sender' SENT_KEY = 'sent' STATUS_KEY = 'status' MSG_TYPE_KEY = 'msg_type' SOURCE_KEY = 'source' SCOPE_KEY = 'scope' RESTRICTION_KEY = 'restriction' ADDRESSES_KEY = 'addresses' CODES_KEY = 'codes' NOTE_KEY = 'note' REFERENCES_KEY = 'references' INCIDENTS_KEY = 'incidents' INFOS_KEY = 'infos' # Returns a Hash representation of an Alert object # # @return [Hash] def to_h RCAP.attribute_values_to_hash( [ CAP_VERSION_KEY, self.class::CAP_VERSION ], [ IDENTIFIER_KEY, @identifier ], [ SENDER_KEY, @sender ], [ SENT_KEY, RCAP.to_s_for_cap( @sent )], [ STATUS_KEY, @status ], [ MSG_TYPE_KEY, @msg_type ], [ SOURCE_KEY, @source ], [ SCOPE_KEY, @scope ], [ RESTRICTION_KEY, @restriction ], [ ADDRESSES_KEY, @addresses ], [ CODES_KEY, @codes ], [ NOTE_KEY, @note ], [ REFERENCES_KEY, @references ], [ INCIDENTS_KEY, @incidents ], [ INFOS_KEY, @infos.map{ |info| info.to_h }]) end # Initialises an Alert object from a Hash produced by Alert#to_h # # @param [Hash] alert_hash # @return [RCAP::CAP_1_0::Alert] def self.from_h( alert_hash ) self.new do |alert| alert.identifier = RCAP.strip_if_given( alert_hash[ IDENTIFIER_KEY ]) alert.sender = RCAP.strip_if_given( alert_hash[ SENDER_KEY ]) alert.sent = RCAP.parse_datetime( alert_hash[ SENT_KEY ]) alert.status = RCAP.strip_if_given( alert_hash[ STATUS_KEY ]) alert.msg_type = RCAP.strip_if_given( alert_hash[ MSG_TYPE_KEY ]) alert.source = RCAP.strip_if_given( alert_hash[ SOURCE_KEY ]) alert.scope = RCAP.strip_if_given( alert_hash[ SCOPE_KEY ]) alert.restriction = RCAP.strip_if_given( alert_hash[ RESTRICTION_KEY ]) Array( alert_hash[ ADDRESSES_KEY ]).each do |address| alert.addresses << address.strip end Array( alert_hash[ CODES_KEY ]).each do |code| alert.codes << code.strip end alert.note = alert_hash[ NOTE_KEY ] Array( alert_hash[ REFERENCES_KEY ]).each do |reference| alert.references << reference.strip end Array( alert_hash[ INCIDENTS_KEY ]).each do |incident| alert.incidents << incident.strip end Array( alert_hash[ INFOS_KEY ]).each do |info_hash| alert.infos << alert.info_class.from_h( info_hash ) end end end # Returns a JSON string representation of an Alert object # # @param [true,false] pretty_print # @return [String] def to_json( pretty_print = false ) if pretty_print JSON.pretty_generate( self.to_h ) else self.to_h.to_json end end # Initialises an Alert object from a JSON string produced by Alert#to_json # # @param [String] json_string # @return [Alert] def self.from_json( json_string ) self.from_h( JSON.parse( json_string )) end end end end