ddr-core 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +12 -0
  3. data/README.md +27 -0
  4. data/Rakefile +30 -0
  5. data/app/assets/config/ddr_core_manifest.js +0 -0
  6. data/app/controllers/users/omniauth_callbacks_controller.rb +11 -0
  7. data/app/controllers/users/sessions_controller.rb +15 -0
  8. data/app/models/concerns/ddr/captionable.rb +25 -0
  9. data/app/models/concerns/ddr/describable.rb +108 -0
  10. data/app/models/concerns/ddr/governable.rb +25 -0
  11. data/app/models/concerns/ddr/has_admin_metadata.rb +141 -0
  12. data/app/models/concerns/ddr/has_attachments.rb +10 -0
  13. data/app/models/concerns/ddr/has_children.rb +10 -0
  14. data/app/models/concerns/ddr/has_content.rb +132 -0
  15. data/app/models/concerns/ddr/has_extracted_text.rb +10 -0
  16. data/app/models/concerns/ddr/has_intermediate_file.rb +25 -0
  17. data/app/models/concerns/ddr/has_multires_image.rb +14 -0
  18. data/app/models/concerns/ddr/has_parent.rb +18 -0
  19. data/app/models/concerns/ddr/has_struct_metadata.rb +21 -0
  20. data/app/models/concerns/ddr/has_thumbnail.rb +33 -0
  21. data/app/models/concerns/ddr/solr_document_behavior.rb +429 -0
  22. data/app/models/concerns/ddr/streamable.rb +25 -0
  23. data/app/models/ddr/admin_set.rb +28 -0
  24. data/app/models/ddr/attachment.rb +14 -0
  25. data/app/models/ddr/collection.rb +28 -0
  26. data/app/models/ddr/component.rb +31 -0
  27. data/app/models/ddr/contact.rb +23 -0
  28. data/app/models/ddr/digest.rb +8 -0
  29. data/app/models/ddr/file.rb +40 -0
  30. data/app/models/ddr/item.rb +36 -0
  31. data/app/models/ddr/language.rb +31 -0
  32. data/app/models/ddr/media_type.rb +22 -0
  33. data/app/models/ddr/resource.rb +94 -0
  34. data/app/models/ddr/rights_statement.rb +25 -0
  35. data/app/models/ddr/target.rb +17 -0
  36. data/config/initializers/devise.rb +262 -0
  37. data/config/locales/ddr-core.en.yml +85 -0
  38. data/config/routes.rb +3 -0
  39. data/db/migrate/20141104181418_create_users.rb +34 -0
  40. data/db/migrate/20141107124012_add_columns_to_user.rb +46 -0
  41. data/lib/ddr-core.rb +1 -0
  42. data/lib/ddr/auth.rb +80 -0
  43. data/lib/ddr/auth/ability.rb +18 -0
  44. data/lib/ddr/auth/ability_definitions.rb +26 -0
  45. data/lib/ddr/auth/ability_definitions/admin_set_ability_definitions.rb +9 -0
  46. data/lib/ddr/auth/ability_definitions/alias_ability_definitions.rb +23 -0
  47. data/lib/ddr/auth/ability_definitions/attachment_ability_definitions.rb +13 -0
  48. data/lib/ddr/auth/ability_definitions/collection_ability_definitions.rb +28 -0
  49. data/lib/ddr/auth/ability_definitions/component_ability_definitions.rb +13 -0
  50. data/lib/ddr/auth/ability_definitions/item_ability_definitions.rb +13 -0
  51. data/lib/ddr/auth/ability_definitions/lock_ability_definitions.rb +13 -0
  52. data/lib/ddr/auth/ability_definitions/publication_ability_definitions.rb +16 -0
  53. data/lib/ddr/auth/ability_definitions/role_based_ability_definitions.rb +39 -0
  54. data/lib/ddr/auth/ability_definitions/superuser_ability_definitions.rb +9 -0
  55. data/lib/ddr/auth/ability_factory.rb +10 -0
  56. data/lib/ddr/auth/abstract_ability.rb +48 -0
  57. data/lib/ddr/auth/affiliation.rb +14 -0
  58. data/lib/ddr/auth/affiliation_groups.rb +20 -0
  59. data/lib/ddr/auth/anonymous_ability.rb +7 -0
  60. data/lib/ddr/auth/auth_context.rb +109 -0
  61. data/lib/ddr/auth/auth_context_factory.rb +13 -0
  62. data/lib/ddr/auth/detached_auth_context.rb +19 -0
  63. data/lib/ddr/auth/dynamic_groups.rb +13 -0
  64. data/lib/ddr/auth/effective_permissions.rb +12 -0
  65. data/lib/ddr/auth/effective_roles.rb +9 -0
  66. data/lib/ddr/auth/failure_app.rb +16 -0
  67. data/lib/ddr/auth/group.rb +40 -0
  68. data/lib/ddr/auth/grouper_gateway.rb +70 -0
  69. data/lib/ddr/auth/groups.rb +32 -0
  70. data/lib/ddr/auth/ldap_gateway.rb +74 -0
  71. data/lib/ddr/auth/permissions.rb +18 -0
  72. data/lib/ddr/auth/remote_groups.rb +14 -0
  73. data/lib/ddr/auth/role_based_access_controls_enforcement.rb +56 -0
  74. data/lib/ddr/auth/roles.rb +28 -0
  75. data/lib/ddr/auth/roles/role.rb +121 -0
  76. data/lib/ddr/auth/roles/role_type.rb +23 -0
  77. data/lib/ddr/auth/roles/role_types.rb +52 -0
  78. data/lib/ddr/auth/superuser_ability.rb +7 -0
  79. data/lib/ddr/auth/test_helpers.rb +22 -0
  80. data/lib/ddr/auth/user.rb +54 -0
  81. data/lib/ddr/auth/web_auth_context.rb +29 -0
  82. data/lib/ddr/core.rb +110 -0
  83. data/lib/ddr/core/engine.rb +8 -0
  84. data/lib/ddr/core/version.rb +5 -0
  85. data/lib/ddr/error.rb +16 -0
  86. data/lib/ddr/files.rb +13 -0
  87. data/lib/ddr/fits.rb +189 -0
  88. data/lib/ddr/index.rb +29 -0
  89. data/lib/ddr/index/abstract_query_result.rb +22 -0
  90. data/lib/ddr/index/connection.rb +38 -0
  91. data/lib/ddr/index/csv_query_result.rb +84 -0
  92. data/lib/ddr/index/document_builder.rb +9 -0
  93. data/lib/ddr/index/field.rb +35 -0
  94. data/lib/ddr/index/field_attribute.rb +22 -0
  95. data/lib/ddr/index/fields.rb +154 -0
  96. data/lib/ddr/index/filter.rb +139 -0
  97. data/lib/ddr/index/query.rb +82 -0
  98. data/lib/ddr/index/query_builder.rb +185 -0
  99. data/lib/ddr/index/query_clause.rb +112 -0
  100. data/lib/ddr/index/query_params.rb +40 -0
  101. data/lib/ddr/index/query_result.rb +102 -0
  102. data/lib/ddr/index/response.rb +30 -0
  103. data/lib/ddr/index/sort_order.rb +28 -0
  104. data/lib/ddr/index/unique_key_field.rb +12 -0
  105. data/lib/ddr/managers.rb +9 -0
  106. data/lib/ddr/managers/manager.rb +13 -0
  107. data/lib/ddr/managers/technical_metadata_manager.rb +141 -0
  108. data/lib/ddr/structure.rb +188 -0
  109. data/lib/ddr/structures/agent.rb +49 -0
  110. data/lib/ddr/structures/component_type_term.rb +29 -0
  111. data/lib/ddr/structures/div.rb +64 -0
  112. data/lib/ddr/structures/f_locat.rb +54 -0
  113. data/lib/ddr/structures/file.rb +52 -0
  114. data/lib/ddr/structures/file_grp.rb +35 -0
  115. data/lib/ddr/structures/file_sec.rb +22 -0
  116. data/lib/ddr/structures/fptr.rb +31 -0
  117. data/lib/ddr/structures/mets_hdr.rb +37 -0
  118. data/lib/ddr/structures/mptr.rb +49 -0
  119. data/lib/ddr/structures/struct_map.rb +40 -0
  120. data/lib/ddr/utils.rb +185 -0
  121. data/lib/ddr/vocab.rb +22 -0
  122. data/lib/ddr/vocab/asset.rb +51 -0
  123. data/lib/ddr/vocab/contact.rb +9 -0
  124. data/lib/ddr/vocab/display.rb +9 -0
  125. data/lib/ddr/vocab/duke_terms.rb +13 -0
  126. data/lib/ddr/vocab/rdf_vocabulary_parser.rb +43 -0
  127. data/lib/ddr/vocab/roles.rb +25 -0
  128. data/lib/ddr/vocab/sources/duketerms.rdf +870 -0
  129. data/lib/ddr/vocab/vocabulary.rb +37 -0
  130. data/lib/ddr/workflow.rb +8 -0
  131. data/lib/tasks/ddr/core_tasks.rake +4 -0
  132. metadata +428 -0
@@ -0,0 +1,85 @@
1
+ en:
2
+ ddr:
3
+ core:
4
+ errors:
5
+ incorrect_resource_class: "%{subject} must be a %{resource_class}"
6
+ index:
7
+ fields:
8
+ aleph_id:
9
+ label: "Aleph ID"
10
+ aspace_id:
11
+ label: "ArchivesSpace ID"
12
+ common_model_name:
13
+ label: "Common Model Name"
14
+ doi:
15
+ label: DOI
16
+ ead_id:
17
+ label: "EAD ID"
18
+ id:
19
+ label: ID
20
+ heading: id
21
+ local_id:
22
+ label: "Local ID"
23
+ permanent_id:
24
+ label: "Permanent ID"
25
+ permanent_url:
26
+ label: "Permanent URL"
27
+ resource_model:
28
+ label: Model
29
+ heading: model
30
+ streamable_media_url:
31
+ label: "Streamable Media URL"
32
+ created_at:
33
+ label: "Creation Date"
34
+ heading: creation_date
35
+ updated_at:
36
+ label: "Modification Date"
37
+ heading: modification_date
38
+ techmd_color_space:
39
+ label: "Color Space"
40
+ heading: color_space
41
+ techmd_creating_application:
42
+ label: "Creating Application"
43
+ heading: creating_application
44
+ techmd_creation_time:
45
+ label: "Creation Time"
46
+ heading: creation_time
47
+ techmd_file_size:
48
+ label: "File Size"
49
+ heading: "file_size"
50
+ techmd_fits_version:
51
+ label: "FITS Version"
52
+ heading: fits_version
53
+ techmd_fits_datetime:
54
+ label: "FITS Run At"
55
+ heading: fits_datetime
56
+ techmd_format_label:
57
+ label: "Format Label"
58
+ heading: format_label
59
+ techmd_format_version:
60
+ label: "Format Version"
61
+ heading: format_version
62
+ techmd_image_height:
63
+ label: "Image Height"
64
+ heading: image_height
65
+ techmd_image_width:
66
+ label: "Image Width"
67
+ heading: image_width
68
+ techmd_md5:
69
+ label: MD5
70
+ heading: md5
71
+ techmd_media_type:
72
+ label: "Media Type"
73
+ heading: media_type
74
+ techmd_modification_time:
75
+ label: "Modification Time"
76
+ heading: modification_time
77
+ techmd_pronom_identifier:
78
+ label: "PRONOM Unique ID"
79
+ heading: "pronom_uid"
80
+ techmd_valid:
81
+ label: Valid
82
+ heading: valid
83
+ techmd_well_formed:
84
+ label: "Well-formed"
85
+ heading: well_formed
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks", sessions: "users/sessions" }
3
+ end
@@ -0,0 +1,34 @@
1
+ class CreateUsers < ActiveRecord::Migration[4.2]
2
+ def up
3
+ unless table_exists?("users")
4
+ create_table "users" do |t|
5
+ t.string "email", default: "", null: false
6
+ t.string "encrypted_password", default: "", null: false
7
+ t.string "reset_password_token"
8
+ t.datetime "reset_password_sent_at"
9
+ t.datetime "remember_created_at"
10
+ t.integer "sign_in_count", default: 0
11
+ t.datetime "current_sign_in_at"
12
+ t.datetime "last_sign_in_at"
13
+ t.string "current_sign_in_ip"
14
+ t.string "last_sign_in_ip"
15
+ t.datetime "created_at", null: false
16
+ t.datetime "updated_at", null: false
17
+ t.string "username", default: "", null: false
18
+ t.string "first_name"
19
+ t.string "middle_name"
20
+ t.string "nickname"
21
+ t.string "last_name"
22
+ t.string "display_name"
23
+ end
24
+
25
+ add_index "users", ["email"], name: "index_users_on_email"
26
+ add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
27
+ add_index "users", ["username"], name: "index_users_on_username", unique: true
28
+ end
29
+ end
30
+
31
+ def down
32
+ raise ActiveRecord::IrreversibleMigration
33
+ end
34
+ end
@@ -0,0 +1,46 @@
1
+ class AddColumnsToUser < ActiveRecord::Migration[4.2]
2
+ def up
3
+ if table_exists?("users")
4
+ unless column_exists?("users", "username")
5
+ add_column "users", "username", :string, default: "", null: false
6
+ end
7
+ unless column_exists?("users", "first_name")
8
+ add_column "users", "first_name", :string
9
+ end
10
+ unless column_exists?("users", "middle_name")
11
+ add_column "users", "middle_name", :string
12
+ end
13
+ unless column_exists?("users", "nickname")
14
+ add_column "users", "nickname", :string
15
+ end
16
+ unless column_exists?("users", "last_name")
17
+ add_column "users", "last_name", :string
18
+ end
19
+ unless column_exists?("users", "display_name")
20
+ add_column "users", "display_name", :string
21
+ end
22
+
23
+ # If the email index exists and is set such that email must be unique (which is the initial
24
+ # setting typically set by Devise(?)), remove and we'll re-add it as non-unique below.
25
+ if index_exists?("users", ["email"])
26
+ if index_exists?("users", ["email"], unique: true)
27
+ remove_index "users", ["email"]
28
+ end
29
+ end
30
+
31
+ # Either the email index didn't exist when we started or, more likely, we removed above
32
+ # because it existed but required email to be unique.
33
+ unless index_exists?("users", ["email"])
34
+ add_index "users", ["email"], name: "index_users_on_email"
35
+ end
36
+
37
+ unless index_exists?("users", ["username"])
38
+ add_index "users", ["username"], name: "index_users_on_username", unique: true
39
+ end
40
+ end
41
+ end
42
+
43
+ def down
44
+ raise ActiveRecord::IrreversibleMigration
45
+ end
46
+ end
@@ -0,0 +1 @@
1
+ require 'ddr/core'
@@ -0,0 +1,80 @@
1
+ module Ddr
2
+ module Auth
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Ability
6
+ autoload :AbilityDefinitions
7
+ autoload :AbilityFactory
8
+ autoload :AbstractAbility
9
+ autoload :Affiliation
10
+ autoload :AffiliationGroups
11
+ autoload :AnonymousAbility
12
+ autoload :AuthContext
13
+ autoload :AuthContextFactory
14
+ autoload :DetachedAuthContext
15
+ autoload :DynamicGroups
16
+ autoload :EffectivePermissions
17
+ autoload :EffectiveRoles
18
+ autoload :FailureApp
19
+ autoload :Group
20
+ autoload :GrouperGateway
21
+ autoload :Groups
22
+ autoload :LdapGateway
23
+ autoload :Permissions
24
+ autoload :RemoteGroups
25
+ autoload :RoleBasedAccessControlsEnforcement
26
+ autoload :Roles
27
+ autoload :SuperuserAbility
28
+ autoload :User
29
+ autoload :WebAuthContext
30
+
31
+ autoload_under 'ability_definitions' do
32
+ autoload :AdminSetAbilityDefinitions
33
+ autoload :AliasAbilityDefinitions
34
+ autoload :AttachmentAbilityDefinitions
35
+ autoload :CollectionAbilityDefinitions
36
+ autoload :ComponentAbilityDefinitions
37
+ autoload :ItemAbilityDefinitions
38
+ autoload :PublicationAbilityDefinitions
39
+ autoload :LockAbilityDefinitions
40
+ autoload :RoleBasedAbilityDefinitions
41
+ autoload :SuperuserAbilityDefinitions
42
+ end
43
+
44
+ # Name of group whose members are authorized to act as superuser
45
+ mattr_accessor :superuser_group
46
+
47
+ # Name of group whose members are authorized to create Collections
48
+ mattr_accessor :collection_creators_group
49
+
50
+ # Name of group whose members are authorized to act as a metadata manager
51
+ mattr_accessor :metadata_managers_group
52
+
53
+ # Whether to require Shibboleth authentication
54
+ mattr_accessor :require_shib_user_authn do
55
+ false
56
+ end
57
+
58
+ # Grouper gateway implementation
59
+ mattr_accessor :grouper_gateway do
60
+ GrouperGateway
61
+ end
62
+
63
+ # LDAP gateway implementation
64
+ mattr_accessor :ldap_gateway do
65
+ LdapGateway
66
+ end
67
+
68
+ mattr_accessor :default_ability do
69
+ "::Ability"
70
+ end
71
+
72
+ def self.repository_group_filter
73
+ if filter = ENV["REPOSITORY_GROUP_FILTER"]
74
+ return filter
75
+ end
76
+ raise Ddr::Error, "The \"REPOSITORY_GROUP_FILTER\" environment variable is not set."
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,18 @@
1
+ module Ddr
2
+ module Auth
3
+ class Ability < AbstractAbility
4
+
5
+ self.ability_definitions = [ AliasAbilityDefinitions,
6
+ CollectionAbilityDefinitions,
7
+ ItemAbilityDefinitions,
8
+ ComponentAbilityDefinitions,
9
+ AttachmentAbilityDefinitions,
10
+ RoleBasedAbilityDefinitions,
11
+ PublicationAbilityDefinitions,
12
+ LockAbilityDefinitions,
13
+ AdminSetAbilityDefinitions,
14
+ ]
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ require "delegate"
2
+
3
+ module Ddr
4
+ module Auth
5
+ #
6
+ # A class which applies ability definitions to the delegated ability class
7
+ # when `#call` is invoked.
8
+ #
9
+ # @abstract
10
+ #
11
+ class AbilityDefinitions < SimpleDelegator
12
+
13
+ # Applies ability definitions to the ability and return it
14
+ def self.call(ability)
15
+ new(ability).call
16
+ ability
17
+ end
18
+
19
+ # Applies abilities definitions with `can` and `cannot`.
20
+ def call
21
+ raise NotImplementedError, "Subclasses must implement `#call`."
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ module Ddr::Auth
2
+ class AdminSetAbilityDefinitions < AbilityDefinitions
3
+
4
+ def call
5
+ can :export, Ddr::AdminSet if metadata_manager?
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ module Ddr
2
+ module Auth
3
+ class AliasAbilityDefinitions < AbilityDefinitions
4
+
5
+ FILE_REQUIRES_DOWNLOAD = %i( content extracted_text )
6
+ DEFAULT_FILE_PERMISSION = :read
7
+
8
+ DOWNLOAD_ALIASES = Ddr::Resource::FILE_FIELDS.each_with_object({}) do |field, memo|
9
+ action = [ :download, field ].join('_').to_sym # e.g., :download_content
10
+ memo[action] = FILE_REQUIRES_DOWNLOAD.include?(field) ? :download : DEFAULT_FILE_PERMISSION
11
+ end
12
+
13
+ def call
14
+ alias_action :upload, to: :replace
15
+ alias_action :add_attachment, to: :add_children
16
+ DOWNLOAD_ALIASES.each do |action, permission|
17
+ alias_action action, to: permission
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ module Ddr
2
+ module Auth
3
+ class AttachmentAbilityDefinitions < AbilityDefinitions
4
+
5
+ def call
6
+ can :create, Ddr::Attachment do |obj|
7
+ obj.attached_to.present? && can?(:add_attachment, obj.attached_to)
8
+ end
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ module Ddr
2
+ module Auth
3
+ class CollectionAbilityDefinitions < AbilityDefinitions
4
+
5
+ def call
6
+ if member_of? Ddr::Auth.collection_creators_group
7
+ can :create, Ddr::Collection
8
+ end
9
+ can :export, Ddr::Collection do |obj|
10
+ has_policy_permission?(obj, Permissions::READ)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def policy_permissions(obj)
17
+ obj.policy_roles
18
+ .select { |r| agents.include?(r.agent) }
19
+ .map(&:permissions).flatten.uniq
20
+ end
21
+
22
+ def has_policy_permission?(obj, perm)
23
+ policy_permissions(obj).include?(perm)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,13 @@
1
+ module Ddr
2
+ module Auth
3
+ class ComponentAbilityDefinitions < AbilityDefinitions
4
+
5
+ def call
6
+ can :create, Ddr::Component do |obj|
7
+ obj.parent.present? && can?(:add_children, obj.parent)
8
+ end
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Ddr
2
+ module Auth
3
+ class ItemAbilityDefinitions < AbilityDefinitions
4
+
5
+ def call
6
+ can :create, Ddr::Item do |obj|
7
+ obj.parent.present? && can?(:add_children, obj.parent)
8
+ end
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Ddr
2
+ module Auth
3
+ class LockAbilityDefinitions < AbilityDefinitions
4
+
5
+ DENIED_WHEN_LOCKED = [ :add_children, :update, :replace, :arrange, :grant ]
6
+
7
+ def call
8
+ cannot DENIED_WHEN_LOCKED, Ddr::Resource, :locked? => true
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module Ddr
2
+ module Auth
3
+ class PublicationAbilityDefinitions < AbilityDefinitions
4
+
5
+ def call
6
+ cannot :publish, Ddr::Resource do |obj|
7
+ obj.published? || !obj.publishable?
8
+ end
9
+ cannot :unpublish, Ddr::Resource do |obj|
10
+ !obj.published?
11
+ end
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ module Ddr
2
+ module Auth
3
+ class RoleBasedAbilityDefinitions < AbilityDefinitions
4
+
5
+ def call
6
+ Permissions::ALL.each do |permission|
7
+ can permission, [ Ddr::Resource, ::SolrDocument, String ] do |obj|
8
+ has_permission? permission, obj
9
+ end
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def has_permission?(permission, object_or_id)
16
+ permissions(object_or_id).include? permission
17
+ end
18
+
19
+ def permissions(object_or_id)
20
+ case object_or_id
21
+ when Ddr::Resource, ::SolrDocument
22
+ cached_permissions(object_or_id.id) do
23
+ object_or_id.effective_permissions(agents)
24
+ end
25
+ when String
26
+ cached_permissions(object_or_id) do
27
+ doc = ::SolrDocument.find(object_or_id) # raises SolrDocument::NotFound
28
+ doc.effective_permissions(agents)
29
+ end
30
+ end
31
+ end
32
+
33
+ def cached_permissions(pid, &block)
34
+ cache[pid] ||= block.call
35
+ end
36
+
37
+ end
38
+ end
39
+ end