shibkit-meta_meta 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +21 -0
  4. data/Gemfile.lock +52 -0
  5. data/Icon.png +0 -0
  6. data/LICENSE.txt +177 -0
  7. data/README.md +789 -0
  8. data/Rakefile +38 -0
  9. data/VERSION +1 -0
  10. data/examples/biggest_entity_id.rb +4 -0
  11. data/lib/shibkit/meta_meta.rb +600 -0
  12. data/lib/shibkit/meta_meta/attribute.rb +73 -0
  13. data/lib/shibkit/meta_meta/config.rb +463 -0
  14. data/lib/shibkit/meta_meta/contact.rb +85 -0
  15. data/lib/shibkit/meta_meta/data/default_metadata/example_federation_metadata.xml +168 -0
  16. data/lib/shibkit/meta_meta/data/default_metadata/local_metadata.xml +66 -0
  17. data/lib/shibkit/meta_meta/data/default_metadata/uncommon_federation_metadata.xml +115 -0
  18. data/lib/shibkit/meta_meta/data/default_metadata_cache.yml +166 -0
  19. data/lib/shibkit/meta_meta/data/dev_sources.yml +86 -0
  20. data/lib/shibkit/meta_meta/data/real_sources.yml +163 -0
  21. data/lib/shibkit/meta_meta/entity.rb +219 -0
  22. data/lib/shibkit/meta_meta/federation.rb +161 -0
  23. data/lib/shibkit/meta_meta/idp.rb +81 -0
  24. data/lib/shibkit/meta_meta/logo.rb +216 -0
  25. data/lib/shibkit/meta_meta/metadata_item.rb +244 -0
  26. data/lib/shibkit/meta_meta/mixin/cached_downloads.rb +127 -0
  27. data/lib/shibkit/meta_meta/mixin/xpath_chores.rb +111 -0
  28. data/lib/shibkit/meta_meta/organisation.rb +73 -0
  29. data/lib/shibkit/meta_meta/provider.rb +195 -0
  30. data/lib/shibkit/meta_meta/provisioning/base.rb +33 -0
  31. data/lib/shibkit/meta_meta/requested_attribute.rb +29 -0
  32. data/lib/shibkit/meta_meta/service.rb +94 -0
  33. data/lib/shibkit/meta_meta/source.rb +558 -0
  34. data/lib/shibkit/meta_meta/sp.rb +79 -0
  35. data/shibkit-meta_meta.gemspec +154 -0
  36. data/spec/meta_meta/attribute/token +0 -0
  37. data/spec/meta_meta/config/autoloading_and_refreshing_spec.rb +72 -0
  38. data/spec/meta_meta/config/code_nspec.rb +13 -0
  39. data/spec/meta_meta/config/configuration_spec.rb +30 -0
  40. data/spec/meta_meta/config/creation_spec.rb +43 -0
  41. data/spec/meta_meta/config/downloading_and_caching_settings_spec.rb +216 -0
  42. data/spec/meta_meta/config/env_platform_settings.rb +129 -0
  43. data/spec/meta_meta/config/filtering_settings_spec.rb +123 -0
  44. data/spec/meta_meta/config/init.rb +8 -0
  45. data/spec/meta_meta/config/logger_settings_spec.rb +91 -0
  46. data/spec/meta_meta/config/smartcache_settings_spec.rb +110 -0
  47. data/spec/meta_meta/config/source_file_settings_spec.rb +99 -0
  48. data/spec/meta_meta/config/tagging_settings_spec.rb +81 -0
  49. data/spec/meta_meta/config/working_directory_settings_spec.rb +106 -0
  50. data/spec/meta_meta/config/xml_processing_settings_spec.rb +75 -0
  51. data/spec/meta_meta/contact/contact_oldspec.rb +0 -0
  52. data/spec/meta_meta/entity/entity_oldspec.rb +53 -0
  53. data/spec/meta_meta/federation/federation_oldspec.rb +0 -0
  54. data/spec/meta_meta/idp/token +0 -0
  55. data/spec/meta_meta/logo/token +0 -0
  56. data/spec/meta_meta/meta_meta/cache_example.yaml +141284 -0
  57. data/spec/meta_meta/meta_meta/meta_meta_spec.rb +269 -0
  58. data/spec/meta_meta/meta_meta/saved_sources.yaml +46 -0
  59. data/spec/meta_meta/metadata_item/token +0 -0
  60. data/spec/meta_meta/organisation/organisation_oldspec.rb +0 -0
  61. data/spec/meta_meta/provider/token +0 -0
  62. data/spec/meta_meta/requested_attribute/token +0 -0
  63. data/spec/meta_meta/service/token +0 -0
  64. data/spec/meta_meta/source/application_extras_spec.rb +234 -0
  65. data/spec/meta_meta/source/conversion_spec.rb +75 -0
  66. data/spec/meta_meta/source/creation_spec.rb +0 -0
  67. data/spec/meta_meta/source/downloads_and_caching_spec.rb +0 -0
  68. data/spec/meta_meta/source/federation_information_spec.rb +11 -0
  69. data/spec/meta_meta/source/fixtures.rb +24 -0
  70. data/spec/meta_meta/source/init.rb +1 -0
  71. data/spec/meta_meta/source/loading_and_saving_spec.rb +0 -0
  72. data/spec/meta_meta/source/metadata_details_spec.rb +0 -0
  73. data/spec/meta_meta/source/metadata_integrity_spec.rb +0 -0
  74. data/spec/meta_meta/source/selection_spec.rb +0 -0
  75. data/spec/meta_meta/source/source_oldspec.rb +353 -0
  76. data/spec/meta_meta/source/xml_parsing_spec.rb +0 -0
  77. data/spec/meta_meta/sp/token +0 -0
  78. data/spec/meta_meta/template +2 -0
  79. data/spec/moi/config_spec.rb +0 -0
  80. data/spec/spec.opts +1 -0
  81. data/spec/spec_helper.rb +25 -0
  82. data/spec/support/supply_xml.rb +0 -0
  83. metadata +320 -0
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "shibkit-meta_meta"
18
+ gem.homepage = "https://github.com/Digital-Identity-Labs/shibkit-meta_meta"
19
+ gem.license = "Apache 2.0"
20
+ gem.summary = %Q{Downloads and parses Shibboleth (SAML2) metadata.}
21
+ gem.description = %Q{Utilities for friendly handling of Shibboleth/SAML2 metadata. Easily download and parse metadata XML into Ruby objects.}
22
+ gem.email = "gems@digitalidentitylabs.com"
23
+ gem.authors = ["Pete Birkinshaw"]
24
+ gem.files.exclude 'lib/scratch_test.rb'
25
+ # dependencies defined in Gemfile
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+
30
+ require 'rspec/core'
31
+ require 'rspec/core/rake_task'
32
+ RSpec::Core::RakeTask.new(:spec) do |spec|
33
+ ##
34
+ spec.pattern = FileList['spec/**/**/*_spec.rb']
35
+ end
36
+
37
+ task :default => :spec
38
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.2
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'shibkit/meta_meta'
3
+
4
+ puts Shibkit::MetaMeta.entities.sort!{|a,b| a.uri.size <=> b.uri.size}.last
@@ -0,0 +1,600 @@
1
+ ## @author Pete Birkinshaw (<pete@digitalidentitylabs.com>)
2
+ ## Copyright: Copyright (c) 2011 Digital Identity Ltd.
3
+ ## License: Apache License, Version 2.0
4
+
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ##
17
+
18
+ require 'rubygems'
19
+
20
+ require 'nokogiri'
21
+ require 'yaml'
22
+ require 'open-uri'
23
+ require 'logger'
24
+ require 'fileutils'
25
+ require 'digest/sha1'
26
+
27
+ require 'shibkit/meta_meta/config'
28
+ require 'shibkit/meta_meta/metadata_item'
29
+ require 'shibkit/meta_meta/contact'
30
+ require 'shibkit/meta_meta/source'
31
+ require 'shibkit/meta_meta/entity'
32
+ require 'shibkit/meta_meta/federation'
33
+ require 'shibkit/meta_meta/organisation'
34
+
35
+ module Shibkit
36
+
37
+ ## Simple library to parse Shibboleth metadata files into Ruby objects
38
+ class MetaMeta
39
+
40
+ ##
41
+ def self.config(&block)
42
+
43
+ if block
44
+ return ::Shibkit::MetaMeta::Config.instance.configure(&block)
45
+ else
46
+ return ::Shibkit::MetaMeta::Config.instance
47
+ end
48
+
49
+ end
50
+
51
+ ## Flush out all available sources, metadata caches, etc.
52
+ def self.reset
53
+
54
+ log.info "Resetting all sources, metadata caches, etc."
55
+
56
+ ## Clear the source data
57
+ @additional_sources = Hash.new
58
+ @loaded_sources = Hash.new
59
+
60
+ ## Clear federation entity data
61
+ self.flush
62
+
63
+ end
64
+
65
+ ## Clear all loaded entity & federation data
66
+ def self.flush
67
+
68
+ log.info "Flushing all loaded objects"
69
+
70
+ @orgs = Array.new
71
+ @entities = Array.new
72
+ @federations = Array.new
73
+ @by_uri = Hash.new
74
+
75
+ end
76
+
77
+ ## Delete all cache files
78
+ def self.delete_all_cached_files!
79
+
80
+ dir = config.cache_root
81
+
82
+ if config.can_delete?
83
+
84
+ log.info "Deleting all files at #{dir}..."
85
+ FileUtils.rm_rf dir
86
+ FileUtils.mkdir_p dir
87
+
88
+ else
89
+
90
+ log.warn "Cannot delete files at #{dir} - check config settings."
91
+
92
+ end
93
+
94
+ end
95
+
96
+ ## Convenience method to add a source from a hash or object
97
+ def self.add_source(data)
98
+
99
+ @additional_sources ||= Hash.new
100
+
101
+ case data
102
+ when Hash
103
+ source = Source.from_hash(data)
104
+ when ::Shibkit::MetaMeta::Source
105
+ source = data
106
+ else
107
+ raise "Expected either hash or Source object!"
108
+ end
109
+
110
+ log.info "Added a source for #{source.uri}"
111
+
112
+ @additional_sources[source.uri] = source
113
+
114
+ end
115
+
116
+ ## Have we loaded any sources?
117
+ def self.loaded_sources?
118
+
119
+ return @loaded_sources ? true : false
120
+
121
+ end
122
+
123
+ ## Load sources from a YAML file
124
+ def self.load_sources(filename=self.config.sources_file)
125
+
126
+ log.info "Loading sources from disk..."
127
+
128
+ @loaded_sources = Hash.new
129
+
130
+ Source.load(filename).each do |source|
131
+
132
+ ## More than one definition for a source is a problem
133
+ raise "Duplicate source for #{source.uri}!" if @loaded_sources[source.uri]
134
+
135
+ @loaded_sources[source.uri] = source
136
+
137
+ end
138
+
139
+ end
140
+
141
+ ## Save all known sources to sources list file
142
+ def self.save_sources(filename)
143
+
144
+ log.info "Saving sources to #{filename}..."
145
+
146
+ src_dump = Hash.new
147
+ self.sources.each { |s| src_dump[s.uri] = s.to_hash }
148
+
149
+ File.open(filename, 'w') { |out| YAML.dump(src_dump, out) }
150
+
151
+ end
152
+
153
+ ## List all sources as an array
154
+ def self.sources
155
+
156
+ if self.config.autoload? and loaded_sources.size == 0 and additional_sources.size == 0
157
+
158
+ self.load_sources
159
+
160
+ end
161
+
162
+ all_sources_indexed = loaded_sources.merge(additional_sources)
163
+
164
+ sources = all_sources_indexed.values
165
+
166
+ sources = sources.sort {|a,b| a.created_at <=> b.created_at }
167
+
168
+ if self.filtered_sources?
169
+
170
+ sources = sources.select { |s| self.selected_federation_uris.include? s.uri }
171
+
172
+ end
173
+
174
+ return sources
175
+
176
+ end
177
+
178
+ ## List of federation/collection uris
179
+ def self.selected_federation_uris
180
+
181
+ return self.config.selected_federation_uris
182
+
183
+ end
184
+
185
+ ## Has a limited subset of federations/sources been selected?
186
+ def self.filtered_sources?
187
+
188
+ return self.selected_federation_uris.empty? ? false : true
189
+
190
+ end
191
+
192
+ ## Loads federation metadata contents
193
+ def self.load_cache_file(file_or_url, format=:yaml)
194
+
195
+ self.reset
196
+
197
+ log.info "Loading object cache file from #{file_or_url} as #{format} data..."
198
+
199
+ @federations = case format
200
+ when :yaml
201
+ YAML.load(File.open(file_or_url))
202
+ when :marshal
203
+ Marshal.load(File.open(file_or_url))
204
+ else
205
+ raise "Unexpected cache file format requested! Please use :yaml or :marshal"
206
+ end
207
+
208
+ self.entities
209
+
210
+ log.info "Processing complete."
211
+
212
+ return true
213
+
214
+ end
215
+
216
+ ## Save entity data into a YAML file.
217
+ def self.save_cache_file(file_path, format=:yaml)
218
+
219
+ log.info "Saving object cache file to #{file_path} as #{format} data..."
220
+
221
+ ## Will *not* overwrite the example/default file in gem! TODO: this code is awful.
222
+ gem_data_path = "#{::File.dirname(__FILE__)}/data"
223
+ if file_path.include? gem_data_path
224
+ raise "Attempt to overwrite gem's default metadata cache! Please specify your own file to save cache in"
225
+ end
226
+
227
+ self.textify_xml!
228
+
229
+ ## Write the YAML to disk
230
+ File.open(file_path, 'w') do |out|
231
+
232
+ case format
233
+ when :yaml
234
+ YAML.dump(@federations, out)
235
+ when :marshal
236
+ Marshal.dump(@federations, out)
237
+ else
238
+ raise "Unexpected cache file format requested! Please use :yaml or :marshal"
239
+ end
240
+
241
+ end
242
+
243
+ return true
244
+
245
+ end
246
+
247
+ ## Parses sources and returns an array of all federation object
248
+ def self.process_sources
249
+
250
+ if config.smartcache_active?
251
+ return if self.smartcache_load
252
+ end
253
+
254
+ log.info "Processing content of sources into objects..."
255
+
256
+ raise "MetaMeta sources are not an Array! (Should not be a #{self.sources.class})" unless
257
+ self.sources.kind_of? Array
258
+
259
+ self.flush
260
+
261
+ self.sources.each do |source|
262
+
263
+ if self.filtered_sources?
264
+
265
+ next unless self.selected_federation_uris.include? source.uri
266
+
267
+ end
268
+
269
+ start_time = Time.new
270
+
271
+ federation = source.to_federation
272
+
273
+ ## Store all federations in array
274
+ @federations << federation
275
+
276
+ log.info "Loaded #{federation.entities.count} entities from #{federation} metadata file in #{Time.new - start_time} seconds."
277
+
278
+
279
+ end
280
+
281
+ ## Bodge to make sure primary ents are set, multifederation calculated, etc
282
+ #self.entities # Issue 14
283
+
284
+ log.info "Processing complete. #{@federations.count} sets of metadata have been loaded."
285
+
286
+ self.smartcache_save if config.smartcache_active?
287
+
288
+ return @federations
289
+
290
+ end
291
+
292
+ ## Downloads and reprocesses metadata files
293
+ def self.refresh(force=false)
294
+
295
+ log.info "Refreshing all selected federations"
296
+
297
+ ## Reload source lists overwriting previous set
298
+ self.load_sources
299
+
300
+ ## Reprocess sources to create fresh set of federation and entity objects
301
+ self.process_sources
302
+
303
+ return true
304
+
305
+ end
306
+
307
+ def self.stockup(force=false)
308
+
309
+ if self.config.autoload?
310
+
311
+ self.process_sources unless @federations
312
+ self.process_sources if @federations.empty?
313
+
314
+ end
315
+
316
+ end
317
+
318
+ ## Have objects been loaded from metadata?
319
+ def self.stocked?
320
+
321
+ return false unless @federations
322
+ return false if @federations.empty?
323
+
324
+ return true
325
+
326
+ end
327
+
328
+ ## Return list of Federations objects (filtered if select_federations is set)
329
+ def self.federations
330
+
331
+ return [] if @federations.nil? and ! config.autoload?
332
+
333
+ self.stockup
334
+
335
+ if self.filtered_sources?
336
+
337
+ return @federations.select { |f| self.selected_federation_uris.include? f.uri }
338
+
339
+ end
340
+
341
+ return @federations
342
+
343
+ end
344
+
345
+ ## All primary entities from all federations
346
+ def self.entities
347
+
348
+ return [] if @entities.nil? and ! config.autoload?
349
+
350
+ ## Populate memoised array of entities if it's empty
351
+ unless @entities and @entities.size > 0
352
+
353
+ ## Array for memoising primary entities
354
+ @entities ||= Array.new
355
+
356
+ ## For keeping track of already processed entities & marking them as primary
357
+ processed = Hash.new
358
+
359
+ self.federations.each do |f|
360
+
361
+ f.entities.each do |e|
362
+
363
+ ## If we've already found the primary version of the entity
364
+ if processed[e.uri]
365
+
366
+ ## Add this federation's URI to the primary
367
+ primary = processed[e.uri]
368
+ primary.other_federation_uris << f.uri
369
+
370
+ ## Add tags from the non-primary to the primary
371
+ primary.tags << e.tags if config.merge_primary_tags?
372
+
373
+ next
374
+
375
+ end
376
+
377
+ ## Mark this entity as the primary and remember it as already processed.
378
+ e.primary = true
379
+ processed[e.uri] = e
380
+
381
+ ## Collect entity
382
+ @entities << e
383
+
384
+ end
385
+
386
+ end
387
+
388
+ ## BODGE: Needs a better fix. Issue 14
389
+ #@entities = @entities.compact
390
+
391
+ end
392
+
393
+ return @entities
394
+
395
+ end
396
+
397
+ def self.orgs
398
+
399
+ unless @orgs and @orgs.size > 0
400
+
401
+ @orgs ||= Array.new
402
+
403
+ processed = Hash.new
404
+
405
+ self.entities.each do |e|
406
+
407
+ org = e.organisation
408
+
409
+ next unless org
410
+ next if processed[org.druid]
411
+
412
+ @orgs << org
413
+
414
+
415
+ processed[org.druid] = true
416
+
417
+ end
418
+
419
+ @orgs.sort! {|a,b| a.druid <=> b.druid }
420
+
421
+ end
422
+
423
+ return @orgs
424
+
425
+ end
426
+
427
+ ##
428
+ def self.idps
429
+
430
+ return entities.select { |e| e.idp? }
431
+
432
+ end
433
+
434
+ ##
435
+ def self.sps
436
+
437
+ return entities.select { |e| e.sp? }
438
+
439
+ end
440
+
441
+ def self.from_uri(uri)
442
+
443
+ unless @by_uri and @by_uri.size > 0
444
+
445
+ @by_uri ||= Hash.new
446
+
447
+ self.federations.each { |f| @by_uri[f.uri] = f unless @by_uri[f.uri] }
448
+ self.entities.each { |e| @by_uri[e.uri] = e unless @by_uri[e.uri] }
449
+
450
+ end
451
+
452
+ return @by_uri[uri]
453
+
454
+ end
455
+
456
+ def self.textify_xml!
457
+
458
+ @federations.each { |f| f.textify_xml(true) }
459
+
460
+ end
461
+
462
+ def self.purge_xml!
463
+
464
+ @federations.each { |f| f.purge_xml(true) }
465
+
466
+ end
467
+
468
+ private
469
+
470
+ ## Logging
471
+ def self.log
472
+
473
+ return ::Shibkit::MetaMeta.config.logger
474
+
475
+ end
476
+
477
+ private
478
+
479
+ ## Access to all additional sources
480
+ def self.additional_sources
481
+
482
+ @additional_sources ||= Hash.new
483
+ return @additional_sources
484
+
485
+ end
486
+
487
+ ## Access to all additional sources
488
+ def self.loaded_sources
489
+
490
+ @loaded_sources ||= Hash.new
491
+ return @loaded_sources
492
+
493
+ end
494
+
495
+ ##
496
+ def self.smartcache_load
497
+
498
+ log.info "Checking smartcache status..."
499
+
500
+ object_file = config.smartcache_object_file
501
+ scmd_file = config.smartcache_info_file
502
+ expiry_period = config.smartcache_expiry
503
+
504
+ ## Do we even have a file?
505
+ return false unless File.exists? object_file
506
+ return false unless File.exists? scmd_file
507
+
508
+ start_time = Time.new
509
+
510
+ ## Make sure the dump metadata is suitable
511
+ info = YAML.load(File.open(scmd_file))
512
+
513
+ ## Check
514
+ cache_age = (Time.new.to_i - info[:created_at].to_i)
515
+ return false unless cache_age < expiry_period.to_i
516
+
517
+ return false unless info[:version] == config.version
518
+
519
+ return false unless info[:platform] == config.platform
520
+
521
+ return false unless info[:format] == :marshal
522
+
523
+ return false unless info[:object_file] == object_file
524
+
525
+ return false unless info[:purge_xml] == config.purge_xml?
526
+ return false unless info[:source_xml] == config.remember_source_xml?
527
+ return false unless info[:groups] == Digest::SHA1.hexdigest(config.selected_groups.join)
528
+
529
+ log.info "Smartcache is valid: loading objects..."
530
+
531
+ ## If file does not exist (or is stale) and we have objects, save
532
+ self.load_cache_file(object_file, :marshal)
533
+
534
+ log.info "Loaded #{@federations.count} federations and #{@entities.count} entities from smartcache in #{Time.new - start_time} seconds."
535
+
536
+ return true
537
+
538
+ end
539
+
540
+ ##
541
+ def self.smartcache_save
542
+
543
+ object_file = config.smartcache_object_file
544
+ scmd_file = config.smartcache_info_file
545
+
546
+ log.info "Saving smartcache with #{@federations.count} federations and #{@entities.count} entities..."
547
+
548
+ ## Save file in fast marsh
549
+ mkdir_p config.cache_root unless File.exists? config.cache_root
550
+ self.save_cache_file(object_file, :marshal)
551
+
552
+ info = {
553
+ :created_at => Time.new,
554
+ :version => config.version,
555
+ :platform => config.platform,
556
+ :object_file => object_file,
557
+ :format => :marshal,
558
+ :purge_xml => config.purge_xml?,
559
+ :source_xml => config.remember_source_xml?,
560
+ :groups => Digest::SHA1.hexdigest(config.selected_groups.join)
561
+ }
562
+
563
+ File.open(scmd_file, 'w') { |out| YAML.dump(info, out) }
564
+
565
+ log.info "Saved smartcache."
566
+
567
+ return true
568
+
569
+ end
570
+
571
+ ## Stats
572
+ def self.stats
573
+
574
+ stats = Hash.new
575
+
576
+ stats[:federations] = Hash.new
577
+
578
+ self.federations.each do |f|
579
+
580
+ stats[:federations][f.uri] = Hash.new
581
+ stats[:federations][f.uri][:sp_count] = f.sps.count
582
+ stats[:federations][f.uri][:idp_count] = f.idps.count
583
+ stats[:federations][f.uri][:entities] = f.entities.count
584
+ stats[:federations][f.uri][:uri_count] = f.entities.collect {|e| e.uri}.uniq.count
585
+
586
+ end
587
+
588
+ stats[:federation_count] = self.federations.count
589
+ stats[:entities_count] = self.federations.inject(0) {|m,f| m+f.entities.count}
590
+ stats[:primary_entities_count] = self.entities.count
591
+ stats[:organisation_count] = self.orgs.count
592
+
593
+ return stats
594
+
595
+ end
596
+
597
+
598
+
599
+ end
600
+ end