ezid-client 1.5.0 → 1.9.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -65,10 +65,25 @@ module Ezid
65
65
  # @return [Ezid::Identifier] the identifier
66
66
  # @raise [Ezid::IdentifierNotFoundError]
67
67
  def modify(id, metadata)
68
- i = allocate
69
- i.id = id
70
- i.update_metadata(metadata)
71
- i.modify!
68
+ allocate.tap do |i|
69
+ i.id = id
70
+ i.update_metadata(metadata)
71
+ i.modify!
72
+ end
73
+ end
74
+
75
+ # Loads an identifier with provided remote metadata
76
+ # The main purpose is to provide an API in a batch processing
77
+ # context to instantiate Identifiers from a BatchDownload.
78
+ # @see #load_metadata!
79
+ # @param id [String] the EZID identifier
80
+ # @param metadata [String, Hash, Ezid::Metadata] the provided metadata
81
+ # @return [Ezid::Identifier] the identifier
82
+ def load(id, metadata = nil)
83
+ allocate.tap do |i|
84
+ i.id = id
85
+ i.load_metadata!(metadata)
86
+ end
72
87
  end
73
88
 
74
89
  # Retrieves an identifier
@@ -76,9 +91,10 @@ module Ezid
76
91
  # @return [Ezid::Identifier] the identifier
77
92
  # @raise [Ezid::IdentifierNotFoundError] if the identifier does not exist in EZID
78
93
  def find(id)
79
- i = allocate
80
- i.id = id
81
- i.load_metadata
94
+ allocate.tap do |i|
95
+ i.id = id
96
+ i.load_metadata
97
+ end
82
98
  end
83
99
  end
84
100
 
@@ -132,9 +148,14 @@ module Ezid
132
148
  # @return [Ezid::Metadata] the metadata
133
149
  def metadata(_=nil)
134
150
  if !_.nil?
135
- warn "[DEPRECATION] The parameter of `metadata` is deprecated and will be removed in 2.0. (called from #{caller.first})"
151
+ warn "[DEPRECATION] The parameter of `metadata` is ignored and will be removed in 2.0. " \
152
+ "(called from #{caller.first})"
136
153
  end
137
- @metadata ||= Metadata.new
154
+ remote_metadata.merge(local_metadata).freeze
155
+ end
156
+
157
+ def local_metadata
158
+ @local_metadata ||= Metadata.new
138
159
  end
139
160
 
140
161
  def remote_metadata
@@ -172,7 +193,7 @@ module Ezid
172
193
  # @param attrs [Hash] the metadata
173
194
  # @return [Ezid::Identifier] the identifier
174
195
  def update_metadata(attrs={})
175
- metadata.update(attrs)
196
+ local_metadata.update(attrs)
176
197
  self
177
198
  end
178
199
 
@@ -203,13 +224,25 @@ module Ezid
203
224
  load_metadata
204
225
  end
205
226
 
206
- # Loads the metadata from EZID
227
+ # Loads the metadata from EZID and marks the identifier as persisted.
207
228
  # @return [Ezid::Identifier] the identifier
208
- # @raise [Ezid::Error]
229
+ # @raise [Ezid::Error] the identifier is not found or other error.
209
230
  def load_metadata
210
231
  response = client.get_identifier_metadata(id)
211
- # self.remote_metadata = Metadata.new(response.metadata)
212
- remote_metadata.replace(response.metadata)
232
+ load_remote_metadata(response.metadata)
233
+ persists!
234
+ self
235
+ end
236
+
237
+ # Loads provided metadata and marks the identifier as persisted.
238
+ # The main purpose is to provide an API in a batch processing
239
+ # context to instantiate Identifiers from a BatchDownload.
240
+ # @see Ezid::BatchEnumerator
241
+ # @see .load
242
+ # @param metadata [String, Hash, Ezid::Metadata] the provided metadata
243
+ # @return [Ezid::Identifier] the identifier
244
+ def load_metadata!(metadata)
245
+ load_remote_metadata(metadata)
213
246
  persists!
214
247
  self
215
248
  end
@@ -287,8 +320,8 @@ module Ezid
287
320
  end
288
321
 
289
322
  def reset_metadata
290
- metadata.clear unless metadata.empty?
291
- remote_metadata.clear unless remote_metadata.empty?
323
+ local_metadata.clear
324
+ remote_metadata.clear
292
325
  end
293
326
 
294
327
  protected
@@ -302,7 +335,7 @@ module Ezid
302
335
  private
303
336
 
304
337
  def local_or_remote_metadata(*args)
305
- value = metadata.send(*args)
338
+ value = local_metadata.send(*args)
306
339
  if value.nil? && persisted?
307
340
  load_metadata if remote_metadata.empty?
308
341
  value = remote_metadata.send(*args)
@@ -311,7 +344,7 @@ module Ezid
311
344
  end
312
345
 
313
346
  def modify
314
- client.modify_identifier(id, metadata)
347
+ client.modify_identifier(id, local_metadata)
315
348
  end
316
349
 
317
350
  def create_or_mint
@@ -319,12 +352,12 @@ module Ezid
319
352
  end
320
353
 
321
354
  def mint
322
- response = client.mint_identifier(shoulder, metadata)
355
+ response = client.mint_identifier(shoulder, local_metadata)
323
356
  self.id = response.id
324
357
  end
325
358
 
326
359
  def create
327
- client.create_identifier(id, metadata)
360
+ client.create_identifier(id, local_metadata)
328
361
  end
329
362
 
330
363
  def persist
@@ -340,5 +373,9 @@ module Ezid
340
373
  update_metadata(self.class.defaults)
341
374
  end
342
375
 
376
+ def load_remote_metadata(metadata)
377
+ remote_metadata.replace(metadata)
378
+ end
379
+
343
380
  end
344
381
  end
data/lib/ezid/metadata.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "hashie"
2
+ require_relative "metadata_transforms/datacite"
2
3
 
3
4
  module Ezid
4
5
  #
@@ -85,7 +86,12 @@ module Ezid
85
86
  end
86
87
 
87
88
  def replace(data)
88
- super coerce(data)
89
+ hsh = coerce(data)
90
+
91
+ # Perform additional profile transforms
92
+ MetadataTransformDatacite.inverse(hsh) if hsh["_profile"] == "datacite"
93
+
94
+ super hsh
89
95
  end
90
96
 
91
97
  # Output metadata in EZID ANVL format
@@ -94,6 +100,10 @@ module Ezid
94
100
  def to_anvl(include_readonly = true)
95
101
  hsh = to_h
96
102
  hsh.reject! { |k, v| READONLY.include?(k) } unless include_readonly
103
+
104
+ # Perform additional profile transforms
105
+ MetadataTransformDatacite.transform(hsh) if profile == "datacite"
106
+
97
107
  lines = hsh.map do |name, value|
98
108
  element = [escape(ESCAPE_NAMES_RE, name), escape(ESCAPE_VALUES_RE, value)]
99
109
  element.join(ANVL_SEPARATOR)
@@ -0,0 +1,72 @@
1
+ require "nokogiri"
2
+
3
+ module Ezid
4
+ class MetadataTransformDatacite
5
+
6
+ # Transforms the provided metadata hash into the appropriate format for datacite. Removes all "datacite.*" keys
7
+ # and transforms these to the appropriate datacite xml. The resultant xml is then added to a single "datacite" key.
8
+ def self.transform(hsh)
9
+ # Render the datacite xml
10
+ resource_opts = {
11
+ "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
12
+ "xmlns" => "http://datacite.org/schema/kernel-4",
13
+ "xsi:schemaLocation" => "http://datacite.org/schema/kernel-4 http://schema.datacite.org/meta/kernel-4/metadata.xsd"
14
+ }
15
+ xml_builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") { |builder|
16
+ builder.resource(resource_opts) {
17
+ builder.identifier(identifierType: hsh["datacite.identifiertype"] || "DOI") {
18
+ builder.text hsh["datacite.identifier"]
19
+ }
20
+ builder.creators {
21
+ builder.creator {
22
+ builder.creatorName hsh["datacite.creator"]
23
+ }
24
+ }
25
+ builder.titles {
26
+ builder.title hsh["datacite.title"]
27
+ }
28
+ builder.publisher hsh["datacite.publisher"]
29
+ builder.publicationYear hsh["datacite.publicationyear"]
30
+ builder.resourceType(resourceTypeGeneral: hsh["datacite.resourcetypegeneral"]) {
31
+ builder.text hsh["datacite.resourcetype"]
32
+ }
33
+ builder.descriptions {
34
+ builder.description(descriptionType: "Abstract") {
35
+ builder.text hsh["datacite.description"]
36
+ }
37
+ }
38
+ }
39
+ }
40
+ # Using this save option to prevent NG from rendering new lines and tabs
41
+ # between nodes. This to help with a cleaner anvl conversion. Similarly,
42
+ # the sub should just remove the new line after the xml header that NG
43
+ # adds, ex:
44
+ # <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resource ...
45
+ xml = xml_builder
46
+ .to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
47
+ .sub("\n", "")
48
+
49
+
50
+ # Transform the hash
51
+ hsh.reject! { |k, v| k =~ /^datacite\./ }
52
+ hsh["datacite"] = xml
53
+ end
54
+
55
+ # Transforms the provided datacite metadata hash into the format appropriate for the Metadata class.
56
+ # Extracts appropriate fields from the datacite xml and creates the corresponding "datacite.*" keys
57
+ def self.inverse(hsh)
58
+ xml = Nokogiri::XML(hsh["datacite"])
59
+ xmlns = "http://datacite.org/schema/kernel-4"
60
+ hsh["datacite.identifier"] = xml.at_xpath("/ns:resource/ns:identifier/text()", ns: xmlns).to_s
61
+ hsh["datacite.identifiertype"] = xml.at_xpath("/ns:resource/ns:identifier/attribute::identifierType", ns: xmlns).to_s
62
+ hsh["datacite.creator"] = xml.at_xpath("/ns:resource/ns:creators/ns:creator/ns:creatorName/text()", ns: xmlns).to_s
63
+ hsh["datacite.title"] = xml.at_xpath("/ns:resource/ns:titles/ns:title/text()", ns: xmlns).to_s
64
+ hsh["datacite.publisher"] = xml.at_xpath("/ns:resource/ns:publisher/text()", ns: xmlns).to_s
65
+ hsh["datacite.publicationyear"] = xml.at_xpath("/ns:resource/ns:publicationYear/text()", ns: xmlns).to_s
66
+ hsh["datacite.resourcetype"] = xml.at_xpath("/ns:resource/ns:resourceType/text()", ns: xmlns).to_s
67
+ hsh["datacite.resourcetypegeneral"] = xml.at_xpath("/ns:resource/ns:resourceType/attribute::resourceTypeGeneral", ns: xmlns).to_s
68
+ hsh["datacite.description"] = xml.at_xpath("/ns:resource/ns:descriptions/ns:description/text()", ns: xmlns).to_s
69
+ hsh.delete("datacite")
70
+ end
71
+ end
72
+ end
@@ -1,9 +1,10 @@
1
- require "delegate"
2
- require "uri"
3
- require "net/http"
4
- require "forwardable"
1
+ require 'delegate'
2
+ require 'uri'
3
+ require 'net/http'
4
+ require 'forwardable'
5
+ require 'date'
5
6
 
6
- require_relative "../responses/response"
7
+ require_relative '../responses/response'
7
8
 
8
9
  module Ezid
9
10
  #
@@ -31,7 +32,7 @@ module Ezid
31
32
  end
32
33
 
33
34
  def short_name
34
- name.split("::").last.sub("Request", "")
35
+ name.split('::').last.sub('Request', '')
35
36
  end
36
37
  end
37
38
 
@@ -42,13 +43,24 @@ module Ezid
42
43
  def initialize(client, *args)
43
44
  @client = client
44
45
  super build_request
45
- set_content_type("text/plain", charset: "UTF-8")
46
+ set_content_type('text/plain', charset: 'UTF-8')
46
47
  end
47
48
 
48
49
  # Executes the request and returns the response
49
50
  # @return [Ezid::Response] the response
50
51
  def execute
51
- response_class.new(get_response_for_request)
52
+ retries = 0
53
+ begin
54
+ response_class.new(get_response_for_request)
55
+ rescue Net::HTTPServerException, UnexpectedResponseError => e
56
+ if retries < 2
57
+ sleep 15
58
+ retries += 1
59
+ retry
60
+ else
61
+ raise
62
+ end
63
+ end
52
64
  end
53
65
 
54
66
  # The request URI
@@ -91,6 +103,7 @@ module Ezid
91
103
 
92
104
  def get_response_for_request
93
105
  connection.start do |conn|
106
+ self['Accept'] = 'text/plain'
94
107
  add_authentication if authentication_required?
95
108
  add_metadata if has_metadata?
96
109
  conn.request(__getobj__)
@@ -113,7 +126,7 @@ module Ezid
113
126
  # Adds authentication data to the request
114
127
  def add_authentication
115
128
  if session.open?
116
- self["Cookie"] = session.cookie
129
+ self['Cookie'] = session.cookie
117
130
  else
118
131
  basic_auth(user, password)
119
132
  end
@@ -14,6 +14,19 @@ module Ezid
14
14
  # Error response status
15
15
  ERROR = "error".freeze
16
16
 
17
+ def initialize(http_response)
18
+ super
19
+
20
+ unless __getobj__.code =~ /2\d\d/
21
+ raise Error, "HTTP response error: %s %s" %
22
+ [ __getobj__.code, __getobj__.message ]
23
+ end
24
+
25
+ unless status_line =~ /^(#{SUCCESS}|#{ERROR}): /
26
+ raise UnexpectedResponseError, __getobj__.body
27
+ end
28
+ end
29
+
17
30
  # The response status -- "success" or "error"
18
31
  # @return [String] the status
19
32
  def status
@@ -0,0 +1,39 @@
1
+ :: ark:/99999/fk4086hs23
2
+ _updated: 1488227717
3
+ _target: http://example.com
4
+ _profile: erc
5
+ _ownergroup: apitest
6
+ _owner: apitest
7
+ _export: yes
8
+ _created: 1488227717
9
+ _status: public
10
+
11
+ :: ark:/99999/fk4086hs23/123
12
+ _updated: 1488227718
13
+ _target: http://ezid.cdlib.org/id/ark:/99999/fk4086hs23/123
14
+ _profile: erc
15
+ _ownergroup: apitest
16
+ _owner: apitest
17
+ _export: yes
18
+ _created: 1488227718
19
+ _status: public
20
+
21
+ :: ark:/99999/fk40p1bb85
22
+ _updated: 1488303565
23
+ _target: http://ezid.cdlib.org/id/ark:/99999/fk40p1bb85
24
+ _profile: erc
25
+ _ownergroup: apitest
26
+ _owner: apitest
27
+ _export: yes
28
+ _created: 1488303565
29
+ _status: public
30
+
31
+ :: ark:/99999/fk40z7fh7x
32
+ _updated: 1488232856
33
+ _target: http://ezid.cdlib.org/id/ark:/99999/fk40z7fh7x
34
+ _profile: erc
35
+ _ownergroup: apitest
36
+ _owner: apitest
37
+ _export: yes
38
+ _created: 1488232856
39
+ _status: public
@@ -0,0 +1 @@
1
+ <?xml version="1.0" encoding="UTF-8"?><resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://datacite.org/schema/kernel-4" xsi:schemaLocation="http://datacite.org/schema/kernel-4 http://schema.datacite.org/meta/kernel-4/metadata.xsd"><identifier identifierType="DOI"></identifier><creators><creator><creatorName/></creator></creators><titles><title/></titles><publisher/><publicationYear/><resourceType resourceTypeGeneral=""></resourceType><descriptions><description descriptionType="Abstract"></description></descriptions></resource>
@@ -0,0 +1 @@
1
+ <?xml version="1.0" encoding="UTF-8"?><resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://datacite.org/schema/kernel-4" xsi:schemaLocation="http://datacite.org/schema/kernel-4 http://schema.datacite.org/meta/kernel-4/metadata.xsd"><identifier identifierType="TestIdentifierType">TestIdentifier</identifier><creators><creator><creatorName>TestCreatorName</creatorName></creator></creators><titles><title>TestTitle</title></titles><publisher>TestPublisher</publisher><publicationYear>TestPublicationYear</publicationYear><resourceType resourceTypeGeneral="TestResourceTypeGeneral">TestResourceType</resourceType><descriptions><description descriptionType="Abstract">TestDescription</description></descriptions></resource>
@@ -0,0 +1,21 @@
1
+ require 'tempfile'
2
+
3
+ module Ezid
4
+ RSpec.describe BatchDownload, ezid: true do
5
+
6
+ subject do
7
+ a_week_ago = (Time.now - (7*24*60*60)).to_i
8
+ described_class.new(:anvl, compression: "zip", permanence: "test", status: "public", createdAfter: a_week_ago)
9
+ end
10
+
11
+ specify {
12
+ expect(subject.download_url).to match(/\Ahttps:\/\/ezid\.cdlib\.org\/download\/\w+\.zip\z/)
13
+ expect(subject.url).to match(/\Ahttps:\/\/ezid\.cdlib\.org\/download\/\w+\.zip\z/)
14
+ Dir.mktmpdir do |tmpdir|
15
+ expect(subject.file(path: tmpdir))
16
+ .to match(/\A#{tmpdir}\/\w+\.zip\z/)
17
+ end
18
+ }
19
+
20
+ end
21
+ end
@@ -1,7 +1,7 @@
1
1
  require "time"
2
2
 
3
3
  module Ezid
4
- RSpec.describe Client, integration: true do
4
+ RSpec.describe Client, ezid: true do
5
5
 
6
6
  shared_examples "an EZID client" do |client|
7
7
  it "should mint and modify" do
@@ -1,5 +1,5 @@
1
1
  module Ezid
2
- RSpec.describe Identifier, integration: true do
2
+ RSpec.describe Identifier, ezid: true do
3
3
 
4
4
  before {
5
5
  @identifier = described_class.mint(TEST_ARK_SHOULDER, target: "http://example.com")
@@ -38,7 +38,8 @@ module Ezid
38
38
  end
39
39
  describe "delete" do
40
40
  subject { described_class.mint(TEST_ARK_SHOULDER, status: "reserved") }
41
- it "deletes the identifier" do
41
+ # Getting 400 Bad Request response - DCS 3/22/21
42
+ xit "deletes the identifier" do
42
43
  subject.delete
43
44
  expect(subject).to be_deleted
44
45
  expect { described_class.find(subject.id) }.to raise_error(IdentifierNotFoundError)
data/spec/spec_helper.rb CHANGED
@@ -15,6 +15,8 @@
15
15
  #
16
16
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
17
 
18
+ require "byebug"
19
+
18
20
  require "rspec/its"
19
21
 
20
22
  require "ezid/test_helper"
@@ -61,7 +63,7 @@ RSpec.configure do |config|
61
63
 
62
64
  # This setting enables warnings. It's recommended, but in some cases may
63
65
  # be too noisy due to issues in dependencies.
64
- config.warnings = true
66
+ config.warnings = false
65
67
 
66
68
  # Many RSpec users commonly either run the entire suite or an individual
67
69
  # file, and it's useful to allow more verbose output when running an
@@ -76,7 +78,7 @@ RSpec.configure do |config|
76
78
  # Print the 10 slowest examples and example groups at the
77
79
  # end of the spec run, to help surface which specs are running
78
80
  # particularly slow.
79
- config.profile_examples = 5
81
+ #config.profile_examples = 5
80
82
 
81
83
  # Run specs in random order to surface order dependencies. If you find an
82
84
  # order dependency and want to debug it, you can fix the order by providing