rcap-rails-generators 1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/CHANGELOG.rdoc +25 -0
  2. data/README.rdoc +248 -0
  3. data/lib/generators/rcap/migrations/migrations_generator.rb +38 -0
  4. data/lib/generators/rcap/migrations/templates/alerts_migration.rb +27 -0
  5. data/lib/generators/rcap/migrations/templates/areas_migration.rb +14 -0
  6. data/lib/generators/rcap/migrations/templates/infos_migration.rb +33 -0
  7. data/lib/generators/rcap/migrations/templates/resources_migration.rb +14 -0
  8. data/lib/generators/rcap/models/models_generator.rb +33 -0
  9. data/lib/generators/rcap/models/templates/models/alert.rb +365 -0
  10. data/lib/generators/rcap/models/templates/models/area.rb +156 -0
  11. data/lib/generators/rcap/models/templates/models/circle.rb +76 -0
  12. data/lib/generators/rcap/models/templates/models/event_code.rb +20 -0
  13. data/lib/generators/rcap/models/templates/models/geocode.rb +20 -0
  14. data/lib/generators/rcap/models/templates/models/info.rb +452 -0
  15. data/lib/generators/rcap/models/templates/models/parameter.rb +64 -0
  16. data/lib/generators/rcap/models/templates/models/point.rb +51 -0
  17. data/lib/generators/rcap/models/templates/models/polygon.rb +75 -0
  18. data/lib/generators/rcap/models/templates/models/resource.rb +143 -0
  19. data/lib/generators/rcap/models/templates/modules/rcap.rb +5 -0
  20. data/lib/generators/rcap/models/templates/modules/validations.rb +116 -0
  21. data/spec/alert_spec.rb +195 -0
  22. data/spec/area_spec.rb +179 -0
  23. data/spec/circle_spec.rb +88 -0
  24. data/spec/geocode_spec.rb +38 -0
  25. data/spec/info_spec.rb +270 -0
  26. data/spec/point_spec.rb +46 -0
  27. data/spec/polygon_spec.rb +68 -0
  28. data/spec/resource_spec.rb +156 -0
  29. data/spec/spec_helper.rb +8 -0
  30. data/spec/utilities_spec.rb +57 -0
  31. data/spec/validations_spec.rb +95 -0
  32. metadata +155 -0
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,25 @@
1
+ = Change Log
2
+
3
+ == 1.0 - 16th March 2011
4
+
5
+ * Added Rails generators [pgeorge]
6
+
7
+ == 0.4 - 6th March 2011
8
+
9
+ * Implemented Hash generation and parsing
10
+ * Implemented JSON generation and parsing
11
+ * Circle is now a subclass of Point
12
+
13
+ == 0.3 - 26th November 2009
14
+
15
+ * Bugfix release
16
+
17
+ == 0.2 - 20th November 2009
18
+
19
+ * Implemented to_s/inspect methods for all classes
20
+ * Implemented YAML generation and parsing
21
+ * Documentation improvements
22
+
23
+ == 0.1 - 5th November 2009
24
+
25
+ * Initial release
data/README.rdoc ADDED
@@ -0,0 +1,248 @@
1
+ = rcap-rails-generators - Generators for the Common Alerting Protocol for Ruby on Rails
2
+
3
+ == Overview
4
+
5
+ The Common Alerting Protocol is a lightweight standard to facilitate the distribution of alerting data. RCAP is an implementation of the CAP in Ruby. It allows for the creation of RCAP messages from Ruby applications and the parsing of external messages.
6
+
7
+ RCAP currently supports only CAP Version 1.1 and rcap-rails-generators only supports Rails 3.X.
8
+
9
+ This gem is a set of generators for creating RCAP ActiveRecord models and migrations.
10
+
11
+ == Version
12
+
13
+ 1.3
14
+
15
+ == Dependencies
16
+
17
+ rcap-rails-generators depends on the following gems
18
+
19
+ * {Assistance}[http://assistance.rubyforge.org]
20
+ * {UUIDTools}[http://uuidtools.rubyforge.org]
21
+ * {JSON}[http://json.rubyforge.org]
22
+
23
+ rcap-rails-generators uses the REXML API, included in Ruby, to parse and generate XML.
24
+
25
+ == Installation
26
+
27
+ rcap-rails-generators is distributed as a Ruby gem and is available from {Gemcutter}[http://gemcutter.org]. If you have Gemcutter set as a source of your gems then rcap-rails-generators can be installed from the command line
28
+
29
+ gem install rcap-rails-generators
30
+
31
+ or via Bundler
32
+
33
+ gem 'rcap-rails-generators'
34
+
35
+ The gem is also available for download and manual installtion at http://www.aimred.com/gems .
36
+
37
+ Once installed you can run:
38
+
39
+ rails generate rcap:models
40
+
41
+ and
42
+
43
+ rails generate rcap:migrations
44
+
45
+ to create the respective files.
46
+
47
+ rcap:models also installs a couple of necessary modules in your Rails.root/lib directory. You will need to make sure they are required:
48
+
49
+ # config/initializers/load_extensions.rb
50
+ require 'rcap'
51
+ require 'validations'
52
+
53
+ == Web resources
54
+
55
+ * The RCAP project page can be found at http://www.aimred.com/projects/rcap
56
+ * The RCAP API docs can be fount at http://www.aimred.com/projects/rcap/api
57
+ * A public git repository can be found at git://github.com/farrel/RCAP.git
58
+
59
+ == Usage
60
+
61
+ To include RCAP into your application add the following require
62
+
63
+ require 'rcap'
64
+
65
+ All RCAP classes reside in the RCAP namespace but including the RCAP module makes the classes available at the top level without the RCAP prefix.
66
+
67
+ alert = RCAP::Alert.new(...
68
+
69
+ include RCAP # Include RCAP module into namespace
70
+ alert = Alert.new(...
71
+
72
+ === Creating an Alert
73
+
74
+ alert = Alert.new( :sender => 'cape_town_disaster_relief@capetown.municipal.za',
75
+ :status => Alert::STATUS_ACTUAL,
76
+ :msg_type => Alert::MSG_TYPE_ALERT,
77
+ :scope => Alert::SCOPE_PUBLIC,
78
+ :infos => Info.new( :event => 'Liquid Petroleoum Tanker Fire',
79
+ :language => 'en-ZA',
80
+ :categories => [ Info::CATEGORY_TRANSPORT, Info::CATEGORY_FIRE ],
81
+ :urgency => Info::URGENCY_IMMEDIATE,
82
+ :severity => Info::SEVERITY_SEVERE,
83
+ :certainty => Info::CERTAINTY_OBSERVED,
84
+ :headline => 'LIQUID PETROLEOUM TANKER FIRE ON N2 INCOMING FREEWAY',
85
+ :description => 'A liquid petroleoum tanker has caught fire on the N2 incoming freeway 1km
86
+ after the R300 interchange. Municipal fire fighting crews have been dispatched.
87
+ Traffic control officers are on the scene and have diverted traffic onto
88
+ alternate routes.' ))
89
+
90
+ # Accessing attributes
91
+ puts alert.status # Print out "Actual"
92
+ puts alert.infos[0].language # Print out "en-ZA"
93
+ puts alert.infos[0].categories.join( ' ' ) # Print out "Transport Fire"
94
+
95
+ === Exporting an Alert
96
+
97
+ ==== To XML
98
+
99
+ Using the alert message created above
100
+
101
+ puts alert.to_xml # Print out CAP XML message
102
+
103
+ Will print the following CAP XML
104
+
105
+ <?xml version='1.0'?>
106
+ <alert xmlns='urn:oasis:names:tc:emergency:cap:1.1'>
107
+ <identifier>494207a7-f86b-4060-8318-a4b2a3ce565e</identifier>
108
+ <sender>cape_town_disaster_relief@capetown.municipal.za</sender>
109
+ <sent>2009-10-26T21:04:51+02:00</sent>
110
+ <status>Actual</status>
111
+ <msgType>Alert</msgType>
112
+ <scope>Public</scope>
113
+ <info>
114
+ <language>en-ZA</language>
115
+ <category>Transport</category>
116
+ <category>Fire</category>
117
+ <event>Liquid Petroleoum Tanker Fire</event>
118
+ <urgency>Immediate</urgency>
119
+ <severity>Severe</severity>
120
+ <certainty>Observed</certainty>
121
+ <headline>LIQUID PETROLEOUM TANKER FIRE ON N2 INCOMING FREEWAY</headline>
122
+ <description>
123
+ A liquid petroleoum tanker has caught fire on the N2 incoming freeway 1km
124
+ after the R300 interchange. Municipal fire fighting crews have been
125
+ dispatched. Traffic control officers are on the scene and have diverted
126
+ traffic onto alternate routes.
127
+ </description>
128
+ </info>
129
+ </alert>
130
+
131
+ ==== To YAML
132
+
133
+ YAML is a plain text serialization format designed to be easily readable and editable by both human and machine. RCAP has custom YAML generation and parsing methods to produce a YAML document that is as human friednly as possible. The following code
134
+
135
+ alert.to_yaml
136
+
137
+ will produce the following YAML document
138
+
139
+ ---
140
+ Identifier: 2a1ba96d-16e4-4f52-85ea-0258c1440bd5
141
+ Sender: cape_town_disaster_relief@capetown.municipal.za
142
+ Sent: 2009-11-19T02:41:29+02:00
143
+ Status: Actual
144
+ Message Type: Alert
145
+ Scope: Public
146
+ Information:
147
+ - Language: en-ZA
148
+ Categories: [Transport, Fire]
149
+ Event: Liquid Petroleoum Tanker Fire
150
+ Urgency: Immediate
151
+ Severity: Severe
152
+ Certainty: Observed
153
+ Headline: LIQUID PETROLEOUM TANKER FIRE ON N2 INCOMING FREEWAY
154
+ Description: |-
155
+ A liquid petroleoum tanker has caught fire on the N2 incoming freeway 1km
156
+ after the R300 interchange. Municipal fire fighting crews have been dispatched.
157
+ Traffic control officers are on the scene and have diverted traffic onto
158
+ alternate routes.
159
+
160
+ Note: If you use Ruby 1.8 the order of the attributes is jumbled due to hashes being unorderd (Ruby 1.9 implements ordered hashes). This does not affect the ability to parse documents generated from RCAP::Alert#to_yaml, it just makes things the output slightly messy.
161
+
162
+ === To JSON
163
+
164
+ JSON(JavaScript Object Notation) is a text serialization format that can be easily loaded in a JavaScript environment.
165
+
166
+ alert.to_json
167
+
168
+ will produce the following JSON string
169
+
170
+ {"identifier":"0eb97e40-195b-437b-9a01-55fe89691def",
171
+ "sender":"cape_town_disaster_relief@capetown.municipal.za",
172
+ "sent":"2011-03-04T15:58:01+02:00",
173
+ "status":"Actual",
174
+ "msg_type":"Alert",
175
+ "scope":"Public",
176
+ "infos":[
177
+ {"language":"en-ZA",
178
+ "categories":["Transport","Fire"],
179
+ "event":"Liquid Petroleoum Tanker Fire",
180
+ "urgency":"Immediate",
181
+ "severity":"Severe",
182
+ "certainty":"Observed",
183
+ "headline":"LIQUID PETROLEOUM TANKER FIRE ON N2 INCOMING FREEWAY",
184
+ "description":"A liquid petroleoum tanker has caught fire on the N2 incoming freeway 1km
185
+ after the R300 interchange. Municipal fire fighting crews have been dispatched.
186
+ Traffic control officers are on the scene and have diverted traffic onto \nalternate routes."}]}
187
+
188
+ === Parsing an Alert From An External Source
189
+
190
+ ==== From XML
191
+
192
+ RCAP allows for the parsing of a CAP XML string
193
+
194
+ alert = RCAP::Alert.from_xml( xml_string )
195
+
196
+ Currently RCAP only supports version 1.1 of the CAP standard and the parser is as strict as possible when parsing data.
197
+
198
+ ==== From YAML
199
+
200
+ Alert messgaes can be read in from text files containing data formatted in YAML as generated by Alert#to_yaml.
201
+
202
+ alert = RCAP::Alert.from_yaml( yaml_string )
203
+
204
+ ==== From JSON
205
+
206
+ An Alert can also be initialised from a JSON string produced by Alert#to_json
207
+
208
+ alert = RCAP::Alert.from_json( json_string )
209
+
210
+ === Validating an alert
211
+
212
+ The RCAP API aims to codify as many of the rules of the CAP XML format into validation rules that can be checked using the Assistance API. The following Info object has two attributes ('severity' and 'certainty') set to incorrect values.
213
+
214
+ info = Info.new( :event => 'Liquid Petroleoum Tanker Fire',
215
+ :language => 'en-ZA',
216
+ :categories => [ Info::CATEGORY_TRANSPORT, Info::CATEGORY_FIRE ],
217
+ :urgency => Info::URGENCY_IMMEDIATE,
218
+ :severity => nil, # Severity is not assigned
219
+ :certainty => 'Unknown Certainty' ) # Certainty is assigned in incorrect value
220
+
221
+ puts "Is info valid: #{ info.valid? }"
222
+ info.errors.full_messages.each{ |message| puts "Error: #{ message }" }
223
+
224
+ Will produce the folling output:
225
+
226
+ Is info valid: false
227
+ Error: severity is not present
228
+ Error: certainty can only be assigned the following values: Observed, Likely, Possible, Unlikely, Unknown
229
+
230
+ All RCAP classes include the Validation module.
231
+
232
+ A full spec suite using {RSpec}[http://www.rspec.info] was used to test the validations and currently numbers over 250 tests.
233
+
234
+ === DateTime and Time
235
+
236
+ It is highly recommended that when dealing with date and time fields (onset, expires etc) that the DateTime class is used to ensure the correct formatting of dates. The Time class can be used when generating a CAP alert XML message however any CAP alert that is parsed from an external XML source will use DateTime by default.
237
+
238
+ == Authors
239
+
240
+ Farrel Lifson - farrel.lifson@aimred.com
241
+
242
+ == License
243
+
244
+ RCAP is released under the BSD License.
245
+
246
+ == Copyright
247
+
248
+ 2009-2011 Aimred CC
@@ -0,0 +1,38 @@
1
+ require 'rails/generators/migration'
2
+
3
+ module Rcap
4
+ module Generators
5
+ class MigrationsGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ desc <<DESC
9
+ Description:
10
+ Generate RCAP migrations.
11
+ DESC
12
+
13
+ def self.source_root
14
+ @_rcap_source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
15
+ end
16
+
17
+ def create_migration_file
18
+ Dir[File.join(MigrationsGenerator.source_root, '*_migration.rb')].each do |file|
19
+ model = file[/(.*\/)?(.*)_migration.rb/, 2]
20
+ migration_template file, "db/migrate/create_#{model}_table.rb"
21
+ Kernel::sleep 1
22
+ end
23
+ end
24
+
25
+ protected
26
+
27
+ no_tasks do
28
+ def self.next_migration_number(dirname) #:nodoc:
29
+ if ActiveRecord::Base.timestamped_migrations
30
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
31
+ else
32
+ "%.3d" % (current_migration_number(dirname) + 1)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ class CreateAlertsTable < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :alerts do |t|
4
+ t.integer :user_id
5
+
6
+ t.string :identifier
7
+ t.string :sender
8
+ t.datetime :sent_at
9
+ t.string :status
10
+ t.string :msg_type
11
+ t.string :source
12
+ t.string :scope
13
+ t.text :restriction
14
+ t.text :addresses
15
+ t.string :code
16
+ t.text :note
17
+ t.text :references
18
+ t.text :incidents
19
+
20
+ t.timestamps
21
+ end
22
+ end
23
+
24
+ def self.down
25
+ drop_table :alerts
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ class CreateAreasTable < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :areas do |t|
4
+ t.integer :alert_id
5
+ t.text :description
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+
11
+ def self.down
12
+ drop_table :areas
13
+ end
14
+ end
@@ -0,0 +1,33 @@
1
+ class CreateInfosTable < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :infos do |t|
4
+ t.integer :alert_id
5
+
6
+ t.string :language
7
+ t.string :categories
8
+ t.string :event
9
+ t.string :response_types
10
+ t.string :urgency
11
+ t.string :severity
12
+ t.string :certainty
13
+ t.string :audience
14
+ t.string :event_codes
15
+ t.datetime :effective_at
16
+ t.datetime :onset_at
17
+ t.datetime :expires_at
18
+ t.string :sender_name
19
+ t.text :headline
20
+ t.text :description
21
+ t.text :instruction
22
+ t.string :web
23
+ t.string :contact
24
+ t.text :parameters
25
+
26
+ t.timestamps
27
+ end
28
+ end
29
+
30
+ def self.down
31
+ drop_table :infos
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ class CreateResourcesTable < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :resources do |t|
4
+ t.integer :alert_id
5
+ t.string :description
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+
11
+ def self.down
12
+ drop_table :resources
13
+ end
14
+ end
@@ -0,0 +1,33 @@
1
+ module Rcap
2
+ module Generators
3
+ class ModelsGenerator < Rails::Generators::Base
4
+ desc <<DESC
5
+ Description:
6
+ Copy RCAP models to your application.
7
+ DESC
8
+
9
+ def self.source_root
10
+ @_rcap_source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
11
+ end
12
+
13
+ def self.banner
14
+ "rails generate rcap:models"
15
+ end
16
+
17
+ def copy_model_files
18
+ directory 'models', 'app/models'
19
+ end
20
+
21
+ def copy_lib_files
22
+ copy_file 'modules/rcap.rb', 'lib/rcap.rb'
23
+ end
24
+
25
+ private
26
+
27
+ def print_usage
28
+ self.class.help(Thor::Base.shell.new)
29
+ exit
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,365 @@
1
+ require 'uuidtools'
2
+
3
+ # An Alert object is valid if
4
+ # * it has an identifier
5
+ # * it has a sender
6
+ # * it has a sent time
7
+ # * it has a valid status value
8
+ # * it has a valid messge type value
9
+ # * it has a valid scope value
10
+ # * all Info objects contained in infos are valid
11
+ class Alert < ActiveRecord::Base
12
+ include Validation
13
+ include RCAP
14
+
15
+ belongs_to :user
16
+ has_many :infos
17
+
18
+ before_validation :set_identifier, :set_sent_at
19
+
20
+ STATUS_ACTUAL = "Actual" # :nodoc:
21
+ STATUS_EXERCISE = "Exercise" # :nodoc:
22
+ STATUS_SYSTEM = "System" # :nodoc:
23
+ STATUS_TEST = "Test" # :nodoc:
24
+ STATUS_DRAFT = "Draft" # :nodoc:
25
+ # Valid values for status
26
+ VALID_STATUSES = [ STATUS_ACTUAL, STATUS_EXERCISE, STATUS_SYSTEM, STATUS_TEST, STATUS_DRAFT ]
27
+
28
+ MSG_TYPE_ALERT = "Alert" # :nodoc:
29
+ MSG_TYPE_UPDATE = "Update" # :nodoc:
30
+ MSG_TYPE_CANCEL = "Cancel" # :nodoc:
31
+ MSG_TYPE_ACK = "Ack" # :nodoc:
32
+ MSG_TYPE_ERROR = "Error" # :nodoc:
33
+ # Valid values for msg_type
34
+ VALID_MSG_TYPES = [ MSG_TYPE_ALERT, MSG_TYPE_UPDATE, MSG_TYPE_CANCEL, MSG_TYPE_ACK, MSG_TYPE_ERROR ]
35
+
36
+ SCOPE_PUBLIC = "Public" # :nodoc:
37
+ SCOPE_RESTRICTED = "Restricted" # :nodoc:
38
+ SCOPE_PRIVATE = "Private" # :nodoc:
39
+ # Valid values for scope
40
+ VALID_SCOPES = [ SCOPE_PUBLIC, SCOPE_PRIVATE, SCOPE_RESTRICTED ]
41
+
42
+ XML_ELEMENT_NAME = 'alert' # :nodoc:
43
+ IDENTIFIER_ELEMENT_NAME = 'identifier' # :nodoc:
44
+ SENDER_ELEMENT_NAME = 'sender' # :nodoc:
45
+ SENT_ELEMENT_NAME = 'sent' # :nodoc:
46
+ STATUS_ELEMENT_NAME = 'status' # :nodoc:
47
+ MSG_TYPE_ELEMENT_NAME = 'msgType' # :nodoc:
48
+ SOURCE_ELEMENT_NAME = 'source' # :nodoc:
49
+ SCOPE_ELEMENT_NAME = 'scope' # :nodoc:
50
+ RESTRICTION_ELEMENT_NAME = 'restriction' # :nodoc:
51
+ ADDRESSES_ELEMENT_NAME = 'addresses' # :nodoc:
52
+ CODE_ELEMENT_NAME = 'code' # :nodoc:
53
+ NOTE_ELEMENT_NAME = 'note' # :nodoc:
54
+ REFERENCES_ELEMENT_NAME = 'references' # :nodoc:
55
+ INCIDENTS_ELEMENT_NAME = 'incidents' # :nodoc:
56
+
57
+ XPATH = 'cap:alert' # :nodoc:
58
+ IDENTIFIER_XPATH = "cap:#{ IDENTIFIER_ELEMENT_NAME }" # :nodoc:
59
+ SENDER_XPATH = "cap:#{ SENDER_ELEMENT_NAME }" # :nodoc:
60
+ SENT_XPATH = "cap:#{ SENT_ELEMENT_NAME }" # :nodoc:
61
+ STATUS_XPATH = "cap:#{ STATUS_ELEMENT_NAME }" # :nodoc:
62
+ MSG_TYPE_XPATH = "cap:#{ MSG_TYPE_ELEMENT_NAME }" # :nodoc:
63
+ SOURCE_XPATH = "cap:#{ SOURCE_ELEMENT_NAME }" # :nodoc:
64
+ SCOPE_XPATH = "cap:#{ SCOPE_ELEMENT_NAME }" # :nodoc:
65
+ RESTRICTION_XPATH = "cap:#{ RESTRICTION_ELEMENT_NAME }" # :nodoc:
66
+ ADDRESSES_XPATH = "cap:#{ ADDRESSES_ELEMENT_NAME }" # :nodoc:
67
+ CODE_XPATH = "cap:#{ CODE_ELEMENT_NAME }" # :nodoc:
68
+ NOTE_XPATH = "cap:#{ NOTE_ELEMENT_NAME }" # :nodoc:
69
+ REFERENCES_XPATH = "cap:#{ REFERENCES_ELEMENT_NAME }" # :nodoc:
70
+ INCIDENTS_XPATH = "cap:#{ INCIDENTS_ELEMENT_NAME }" # :nodoc:
71
+
72
+ # # If not set a UUID will be set by default
73
+ # attr_accessor( :identifier)
74
+ # attr_accessor( :sender )
75
+ # # Sent Time - If not set will value will be time of creation.
76
+ # attr_accessor( :sent )
77
+ # # Value can only be one of VALID_STATUSES
78
+ # attr_accessor( :status )
79
+ # # Value can only be one of VALID_MSG_TYPES
80
+ # attr_accessor( :msg_type )
81
+ # # Value can only be one of VALID_SCOPES
82
+ # attr_accessor( :scope )
83
+ # attr_accessor( :source )
84
+ # # Depends on scope being SCOPE_RESTRICTED.
85
+ # attr_accessor( :restriction )
86
+ # attr_accessor( :code )
87
+ # attr_accessor( :note )
88
+ #
89
+ # # Collection of address strings. Depends on scope being SCOPE_PRIVATE.
90
+ # attr_reader( :addresses )
91
+ # # Collection of reference strings - see Alert#to_reference
92
+ # attr_reader( :references)
93
+ # # Collection of incident strings
94
+ # attr_reader( :incidents )
95
+ # # Collection of Info objects
96
+ # attr_reader( :infos )
97
+
98
+ validates_presence_of( :identifier, :sender, :sent_at, :status, :msg_type, :scope )
99
+
100
+ validates_inclusion_of( :status, :in => VALID_STATUSES )
101
+ validates_inclusion_of( :msg_type, :in => VALID_MSG_TYPES )
102
+ validates_inclusion_of( :scope, :in => VALID_SCOPES )
103
+
104
+ validates_format_of( :identifier, :with => ALLOWED_CHARACTERS )
105
+ validates_format_of( :sender , :with => ALLOWED_CHARACTERS )
106
+
107
+ validates_dependency_of( :addresses, :on => :scope, :with_value => SCOPE_PRIVATE )
108
+ validates_dependency_of( :restriction, :on => :scope, :with_value => SCOPE_RESTRICTED )
109
+
110
+ # validates_collection_of( :infos )
111
+
112
+ # def initialize( attributes = {})
113
+ # attributes = {} unless attributes
114
+ # @identifier = attributes[ :identifier ] || UUIDTools::UUID.random_create.to_s
115
+ # @sender = attributes[ :sender ]
116
+ # @sent = attributes[ :sent ] || DateTime.now
117
+ # @status = attributes[ :status ]
118
+ # @msg_type = attributes[ :msg_type ]
119
+ # @scope = attributes[ :scope ]
120
+ # @source = attributes[ :source ]
121
+ # @restriction = attributes[ :restriction ]
122
+ # @addresses = Array( attributes[ :addresses ])
123
+ # @references = Array( attributes[ :references ])
124
+ # @incidents = Array( attributes[ :incidents ])
125
+ # @infos = Array( attributes[ :infos ])
126
+ # end
127
+
128
+ def to_xml_element #:nodoc:
129
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
130
+ xml_element.add_namespace( RCAP::XMLNS )
131
+ xml_element.add_element( IDENTIFIER_ELEMENT_NAME ).add_text( self.identifier )
132
+ xml_element.add_element( SENDER_ELEMENT_NAME ).add_text( self.sender )
133
+ xml_element.add_element( SENT_ELEMENT_NAME ).add_text( self.sent.to_s )
134
+ xml_element.add_element( STATUS_ELEMENT_NAME ).add_text( self.status )
135
+ xml_element.add_element( MSG_TYPE_ELEMENT_NAME ).add_text( self.msg_type )
136
+ xml_element.add_element( SOURCE_ELEMENT_NAME ).add_text( self.source ) if self.source
137
+ xml_element.add_element( SCOPE_ELEMENT_NAME ).add_text( self.scope )
138
+ xml_element.add_element( RESTRICTION_ELEMENT_NAME ).add_text( self.restriction ) if self.restriction
139
+ unless self.addresses.empty?
140
+ xml_element.add_element( ADDRESSES_ELEMENT_NAME ).add_text( self.addresses.to_s_for_cap )
141
+ end
142
+ xml_element.add_element( CODE_ELEMENT_NAME ).add_text( self.code ) if self.code
143
+ xml_element.add_element( NOTE_ELEMENT_NAME ).add_text( self.note ) if self.note
144
+ unless self.references.empty?
145
+ xml_element.add_element( REFERENCES_ELEMENT_NAME ).add_text( self.references.join( ' ' ))
146
+ end
147
+ unless self.incidents.empty?
148
+ xml_element.add_element( INCIDENTS_ELEMENT_NAME ).add_text( self.incidents.join( ' ' ))
149
+ end
150
+ self.infos.each do |info|
151
+ xml_element.add_element( info.to_xml_element )
152
+ end
153
+ xml_element
154
+ end
155
+
156
+ def to_xml_document #:nodoc:
157
+ xml_document = REXML::Document.new
158
+ xml_document.add( REXML::XMLDecl.new )
159
+ xml_document.add( self.to_xml_element )
160
+ xml_document
161
+ end
162
+
163
+ # Returns a string containing the XML representation of the alert.
164
+ def to_xml
165
+ self.to_xml_document.to_s
166
+ end
167
+
168
+ # Returns a string representation of the alert suitable for usage as a reference in a CAP message of the form
169
+ # sender,identifier,sent
170
+ def to_reference
171
+ "#{ self.sender },#{ self.identifier },#{ self.sent }"
172
+ end
173
+
174
+ def inspect # :nodoc:
175
+ alert_inspect = <<EOF
176
+ CAP Version: #{ RCAP::CAP_VERSION }
177
+ Identifier: #{ self.identifier }
178
+ Sender: #{ self.sender }
179
+ Sent: #{ self.sent_at }
180
+ Status: #{ self.status }
181
+ Message Type: #{ self.msg_type }
182
+ Source: #{ self.source }
183
+ Scope: #{ self.scope }
184
+ Restriction: #{ self.restriction }
185
+ Addresses: #{ self.addresses.to_s_for_cap if self.addresses }
186
+ Code: #{ self.code }
187
+ Note: #{ self.note }
188
+ References: #{ self.references.join( ' ' ) if self.references }
189
+ Incidents: #{ self.incidents.join( ' ') if self.incidents }
190
+ Information:
191
+ #{ self.infos.map{ |info| " " + info.to_s }.join( "\n" )}
192
+ EOF
193
+ RCAP.format_lines_for_inspect( 'ALERT', alert_inspect )
194
+ end
195
+
196
+ # Returns a string representation of the alert of the form
197
+ # sender/identifier/sent
198
+ # See Alert#to_reference for another string representation suitable as a CAP reference.
199
+ def to_s
200
+ "#{ self.sender }/#{ self.identifier }/#{ self.sent }"
201
+ end
202
+
203
+ def self.from_xml_element( alert_xml_element ) # :nodoc:
204
+ self.new( :identifier => RCAP.xpath_text( alert_xml_element, RCAP::Alert::IDENTIFIER_XPATH ),
205
+ :sender => RCAP.xpath_text( alert_xml_element, SENDER_XPATH ),
206
+ :sent_at => (( sent = RCAP.xpath_first( alert_xml_element, SENT_XPATH )) ? DateTime.parse( sent.text ) : nil ),
207
+ :status => RCAP.xpath_text( alert_xml_element, STATUS_XPATH ),
208
+ :msg_type => RCAP.xpath_text( alert_xml_element, MSG_TYPE_XPATH ),
209
+ :source => RCAP.xpath_text( alert_xml_element, SOURCE_XPATH ),
210
+ :scope => RCAP.xpath_text( alert_xml_element, SCOPE_XPATH ),
211
+ :restriction => RCAP.xpath_text( alert_xml_element, RESTRICTION_XPATH ),
212
+ :addresses => (( address = RCAP.xpath_text( alert_xml_element, ADDRESSES_XPATH )) ? address.unpack_cap_list : nil ),
213
+ :code => RCAP.xpath_text( alert_xml_element, CODE_XPATH ),
214
+ :note => RCAP.xpath_text( alert_xml_element, NOTE_XPATH ),
215
+ :references => (( references = RCAP.xpath_text( alert_xml_element, REFERENCES_XPATH )) ? references.split( ' ' ) : nil ),
216
+ :incidents => (( incidents = RCAP.xpath_text( alert_xml_element, INCIDENTS_XPATH )) ? incidents.split( ' ' ) : nil )
217
+ # :infos => RCAP.xpath_match( alert_xml_element, RCAP::Info::XPATH ).map{ |element| RCAP::Info.from_xml_element( element )}
218
+ )
219
+ end
220
+
221
+ def self.from_xml_document( xml_document ) # :nodoc:
222
+ self.from_xml_element( xml_document.root )
223
+ end
224
+
225
+ # Initialise an Alert object from an XML string. Any object that is a subclass of IO (e.g. File) can be passed in.
226
+ def self.from_xml( xml )
227
+ self.from_xml_document( REXML::Document.new( xml ))
228
+ end
229
+
230
+ CAP_VERSION_YAML = "CAP Version" # :nodoc:
231
+ IDENTIFIER_YAML = "Identifier" # :nodoc:
232
+ SENDER_YAML = "Sender" # :nodoc:
233
+ SENT_YAML = "Sent" # :nodoc:
234
+ STATUS_YAML = "Status" # :nodoc:
235
+ MSG_TYPE_YAML = "Message Type" # :nodoc:
236
+ SOURCE_YAML = "Source" # :nodoc:
237
+ SCOPE_YAML = "Scope" # :nodoc:
238
+ RESTRICTION_YAML = "Restriction" # :nodoc:
239
+ ADDRESSES_YAML = "Addresses" # :nodoc:
240
+ CODE_YAML = "Code" # :nodoc:
241
+ NOTE_YAML = "Note" # :nodoc:
242
+ REFERENCES_YAML = "References" # :nodoc:
243
+ INCIDENTS_YAML = "Incidents" # :nodoc:
244
+ INFOS_YAML = "Information" # :nodoc:
245
+
246
+ # Returns a string containing the YAML representation of the alert.
247
+ def to_yaml( options = {} )
248
+ RCAP.attribute_values_to_hash(
249
+ [ CAP_VERSION_YAML, RCAP::CAP_VERSION ],
250
+ [ IDENTIFIER_YAML, self.identifier ],
251
+ [ SENDER_YAML, self.sender ],
252
+ [ SENT_YAML, self.sent_at ],
253
+ [ STATUS_YAML, self.status ],
254
+ [ MSG_TYPE_YAML, self.msg_type ],
255
+ [ SOURCE_YAML, self.source ],
256
+ [ SCOPE_YAML, self.scope ],
257
+ [ RESTRICTION_YAML, self.restriction ],
258
+ [ ADDRESSES_YAML, self.addresses ],
259
+ [ CODE_YAML, self.code ],
260
+ [ NOTE_YAML, self.note ],
261
+ [ REFERENCES_YAML, self.references ],
262
+ [ INCIDENTS_YAML, self.incidents ],
263
+ [ INFOS_YAML, self.infos ]
264
+ ).to_yaml( options )
265
+ end
266
+
267
+ # Initialise an Alert object from a YAML string. Any object that is a subclass of IO (e.g. File) can be passed in.
268
+ def self.from_yaml( yaml )
269
+ self.from_yaml_data( YAML.load( yaml ))
270
+ end
271
+
272
+ def self.from_yaml_data( alert_yaml_data ) # :nodoc:
273
+ Alert.new(
274
+ :identifier => alert_yaml_data[ IDENTIFIER_YAML ],
275
+ :sender => alert_yaml_data[ SENDER_YAML ],
276
+ :sent_at => ( sent = alert_yaml_data[ SENT_YAML ]).blank? ? nil : DateTime.parse( sent.to_s ),
277
+ :status => alert_yaml_data[ STATUS_YAML ],
278
+ :msg_type => alert_yaml_data[ MSG_TYPE_YAML ],
279
+ :source => alert_yaml_data[ SOURCE_YAML ],
280
+ :scope => alert_yaml_data[ SCOPE_YAML ],
281
+ :restriction => alert_yaml_data[ RESTRICTION_YAML ],
282
+ :addresses => alert_yaml_data[ ADDRESSES_YAML ],
283
+ :code => alert_yaml_data[ CODE_YAML ],
284
+ :note => alert_yaml_data[ NOTE_YAML ],
285
+ :references => alert_yaml_data[ REFERENCES_YAML ],
286
+ :incidents => alert_yaml_data[ INCIDENTS_YAML ]
287
+ # :infos => Array( alert_yaml_data[ INFOS_YAML ]).map{ |info_yaml_data| RCAP::Info.from_yaml_data( info_yaml_data )}
288
+ )
289
+ end
290
+
291
+ CAP_VERSION_KEY = 'cap_version' # :nodoc:
292
+ IDENTIFIER_KEY = 'identifier' # :nodoc:
293
+ SENDER_KEY = 'sender' # :nodoc:
294
+ SENT_KEY = 'sent' # :nodoc:
295
+ STATUS_KEY = 'status' # :nodoc:
296
+ MSG_TYPE_KEY = 'msg_type' # :nodoc:
297
+ SOURCE_KEY = 'source' # :nodoc:
298
+ SCOPE_KEY = 'scope' # :nodoc:
299
+ RESTRICTION_KEY = 'restriction' # :nodoc:
300
+ ADDRESSES_KEY = 'addresses' # :nodoc:
301
+ CODE_KEY = 'code' # :nodoc:
302
+ NOTE_KEY = 'note' # :nodoc:
303
+ REFERENCES_KEY = 'references' # :nodoc:
304
+ INCIDENTS_KEY = 'incidents' # :nodoc:
305
+ INFOS_KEY = 'infos' # :nodoc:
306
+
307
+ # Returns a Hash representation of an Alert object
308
+ def to_h
309
+ RCAP.attribute_values_to_hash( [ CAP_VERSION_KEY, RCAP::CAP_VERSION ],
310
+ [ IDENTIFIER_KEY, self.identifier ],
311
+ [ SENDER_KEY, self.sender ],
312
+ [ SENT_KEY, RCAP.to_s_for_cap( self.sent )],
313
+ [ STATUS_KEY, self.status ],
314
+ [ MSG_TYPE_KEY, self.msg_type ],
315
+ [ SOURCE_KEY, self.source ],
316
+ [ SCOPE_KEY, self.scope ],
317
+ [ RESTRICTION_KEY, self.restriction ],
318
+ [ ADDRESSES_KEY, self.addresses ],
319
+ [ CODE_KEY, self.code ],
320
+ [ NOTE_KEY, self.note ],
321
+ [ REFERENCES_KEY, self.references ],
322
+ [ INCIDENTS_KEY, self.incidents ],
323
+ [ INFOS_KEY, self.infos.map{ |info| info.to_h }])
324
+ end
325
+
326
+ # Initialises an Alert object from a Hash produced by Alert#to_h
327
+ def self.from_h( alert_hash )
328
+ self.new(
329
+ :identifier => alert_hash[ IDENTIFIER_KEY ],
330
+ :sender => alert_hash[ SENDER_KEY ],
331
+ :sent_at => RCAP.parse_datetime( alert_hash[ SENT_KEY ]),
332
+ :status => alert_hash[ STATUS_KEY ],
333
+ :msg_type => alert_hash[ MSG_TYPE_KEY ],
334
+ :source => alert_hash[ SOURCE_KEY ],
335
+ :scope => alert_hash[ SCOPE_KEY ],
336
+ :restriction => alert_hash[ RESTRICTION_KEY ],
337
+ :addresses => alert_hash[ ADDRESSES_KEY ],
338
+ :code => alert_hash[ CODE_KEY ],
339
+ :note => alert_hash[ NOTE_KEY ],
340
+ :references => alert_hash[ REFERENCES_KEY ],
341
+ :incidents => alert_hash[ INCIDENTS_KEY ]
342
+ # :infos => Array( alert_hash[ INFOS_KEY ]).map{ |info_hash| RCAP::Info.from_h( info_hash )}
343
+ )
344
+ end
345
+
346
+ # Returns a JSON string representation of an Alert object
347
+ def to_json
348
+ self.to_h.to_json
349
+ end
350
+
351
+ # Initiialises an Alert object from a JSON string produced by Alert#to_json
352
+ def self.from_json( json_string )
353
+ self.from_h( JSON.parse( json_string ))
354
+ end
355
+
356
+ private
357
+
358
+ def set_identifier
359
+ self.identifier = UUIDTools::UUID.random_create.to_s if identifier.blank?
360
+ end
361
+
362
+ def set_sent_at
363
+ self.sent_at = Time.now if sent_at.blank?
364
+ end
365
+ end