oai_repository 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 Intersect Australia Ltd.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = OAI Repository
2
+
3
+ A Rails (3.1+) engine that allows you to expose your models through an OAI-PHM Data Provider interface.
4
+
5
+ See http://www.oaforum.org/tutorial/ and http://www.openarchives.org/OAI/openarchivesprotocol.html#Repository
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'OaiRepository'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require_tree .
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,4 @@
1
+ module OaiRepository
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,18 @@
1
+ module OaiRepository
2
+ class ServicesController < ApplicationController
3
+ require 'oai_provider'
4
+
5
+ def show
6
+
7
+ options = params.delete_if { |k,v| %w{controller action}.include?(k) }
8
+ response = get_provider.process_request(options)
9
+ render :xml => response
10
+
11
+ end
12
+
13
+ def get_provider
14
+ @provider ||= OAIProvider::provider.new
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,4 @@
1
+ module OaiRepository
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module OaiRepository
2
+ module ServicesHelper
3
+ end
4
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>OaiRepository</title>
5
+ <%= stylesheet_link_tag "oai_repository/application", :media => "all" %>
6
+ <%= javascript_include_tag "oai_repository/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,2 @@
1
+ <h1>Services#show</h1>
2
+ <p>Find me in app/views/oai_repository/services/show.html.erb</p>
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ OaiRepository::Engine.routes.draw do
2
+
3
+ get "services/show"
4
+
5
+ root :to => "services#show"
6
+
7
+ end
@@ -0,0 +1,156 @@
1
+ include OAI::Provider
2
+ class ARWrapperModel < OAI::Provider::Model
3
+
4
+ def initialize(options={})
5
+ @timestamp_field = options.delete(:timestamp_field) || 'updated_at'
6
+ @limit = options.delete(:limit)
7
+ @sets_map = options.delete(:sets)
8
+ @oai_dc_mapping = {}
9
+
10
+ unless options.empty?
11
+ raise ArgumentError.new(
12
+ "Unsupported options [#{options.keys.join(', ')}]"
13
+ )
14
+ end
15
+
16
+ end
17
+
18
+ def map_oai_dc
19
+ if @oai_dc_mapping.empty?
20
+ dc = OAI::Provider::Metadata::DublinCore.instance
21
+ dc.fields.map do |field|
22
+ @oai_dc_mapping[field.to_sym] = "oai_dc_#{field}".to_sym
23
+ end
24
+ end
25
+ @oai_dc_mapping
26
+ end
27
+
28
+ def sets
29
+ @sets_map.map {|set_obj|
30
+ OAI::Set.new(
31
+ {
32
+ name: set_obj[:name],
33
+ spec: set_obj[:spec],
34
+ description: set_obj[:description]
35
+ }
36
+ )
37
+ }
38
+ end
39
+
40
+ def earliest
41
+ record_end(:asc)
42
+ end
43
+
44
+ def latest
45
+ record_end(:desc)
46
+ end
47
+
48
+ # selector can be id or :all
49
+ def find(selector, options={})
50
+ token = nil
51
+ if options[:resumption_token]
52
+ raise OAI::ResumptionTokenException.new unless @limit
53
+ token = ResumptionToken.parse(options[:resumption_token])
54
+ end
55
+
56
+ from = token ? token.from : options[:from]
57
+ to = token ? token.until : options[:until]
58
+ last = token ? token.last : 0
59
+ prefix = token ? token.prefix : options[:metadata_prefix]
60
+ set = token ? token.set : options[:set]
61
+
62
+ conditions = sql_conditions(:from => from, :until => to)
63
+ record_rows = get_record_rows(get_models(set), conditions)
64
+
65
+ return get_specific_record(record_rows, selector) if selector != :all
66
+
67
+ if @limit and record_rows.size > @limit
68
+ get_paged_records(record_rows, :from => from, :until => to, :set => set, :last => last, :metadata_prefix => prefix)
69
+ else
70
+ get_record_objects(record_rows)
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def get_specific_record(records, id)
77
+ # TODO: optimise somehow
78
+ # This is terribly intensive :-(
79
+ records.each do |record|
80
+ obj = Object.const_get(record["type"]).find(record["id"])
81
+ return obj if obj.oai_dc_identifier.eql?(id)
82
+ end
83
+ raise OAI::IdException.new
84
+ end
85
+
86
+ def get_paged_records(record_rows, options)
87
+
88
+ if record_rows.size < options[:last]
89
+ raise OAI::ResumptionTokenException.new
90
+ end
91
+
92
+ list = get_record_objects(record_rows[options[:last], @limit])
93
+
94
+ if list.size < @limit
95
+ list
96
+ else
97
+ options[:last] += @limit
98
+ PartialResult.new(list, ResumptionToken.new(options))
99
+ end
100
+ end
101
+
102
+ def sql_conditions(opts)
103
+ from = Time.parse(opts[:from].to_s).localtime
104
+ to = Time.parse(opts[:until].to_s).localtime.to_s
105
+ return "#{timestamp_field} >= #{ActiveRecord::Base.sanitize(from)} AND #{timestamp_field} <= #{ActiveRecord::Base.sanitize(to)}"
106
+ end
107
+
108
+ def get_models(set)
109
+ models =
110
+ if set.nil?
111
+ @sets_map.map{|s| s[:model]}
112
+ else
113
+ @sets_map.select{|s| s[:spec] == set}.map{|s| s[:model]}
114
+ end
115
+
116
+ if models.empty?
117
+ raise OAI::NoMatchException.new
118
+ end
119
+ models
120
+ end
121
+
122
+ def get_record_rows(models, conditions)
123
+ record_sql = models.map do |m|
124
+ "select id, '#{m.name}' as type, #{timestamp_field} from #{m.table_name} where #{conditions}"
125
+ end.join(" UNION ")
126
+
127
+ sorted_list_sql = "select t.id as id, t.type as type, t.updated_at as updated_at from (#{record_sql}) t order by t.updated_at desc"
128
+ records = ActiveRecord::Base.connection.execute(sorted_list_sql).to_a
129
+ end
130
+
131
+ def get_record_objects(records)
132
+ records.map do |record|
133
+ Object.const_get(record["type"]).find(record["id"])
134
+ end
135
+ end
136
+
137
+ def record_end(order)
138
+ record = nil
139
+ @sets_map.each do |s|
140
+ r = s[:model].first(:order => "#{@timestamp_field} #{order.to_s}")
141
+ next if r.nil?
142
+
143
+ if record.nil?
144
+ record = r
145
+ elsif order == :asc and r.send(@timestamp_field) < record.send(@timestamp_field)
146
+ record = r
147
+ elsif order == :desc and r.send(@timestamp_field) > record.send(@timestamp_field)
148
+ record = r
149
+ end
150
+ end
151
+ raise OAI::NoMatchException if record.nil?
152
+ record.send(@timestamp_field)
153
+ end
154
+
155
+ end
156
+
@@ -0,0 +1,12 @@
1
+ module OaiRepository
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("../../templates", __FILE__)
5
+
6
+ desc "Creates an OAI Repository initializer."
7
+ def copy_initializer
8
+ template "oai_repository.rb", "config/initializers/oai_repository.rb"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,45 @@
1
+ require 'rifcs_format'
2
+ OaiRepository.setup do |config|
3
+
4
+ config.repository_name = 'NAME YOUR REPOSITORY'
5
+
6
+ # The URL from which this OAI Repository is served.
7
+ # If you're deploying to different hostnames (e.g. development, QA and
8
+ # production environments, each with different hostnames), you could
9
+ # conditionally set this.
10
+ config.repository_url = 'URL OF THE REPOSITORY'
11
+
12
+ # By default the (unique) identifier of each record will be composed as
13
+ # #{record_prefix}/#{record.id}
14
+ # This is probably not want you want, especially if you have multiple record
15
+ # sets (i.e. this provider serves multiple ActiveRecord models)
16
+ #
17
+ # Most probably you'll create an oai_dc_identifier attribute or method in
18
+ # the AR models you intend to serve. That value will supplant the default.
19
+ config.record_prefix = 'http://localhost:3000/'
20
+
21
+ config.admin_email = 'change_me@example.com'
22
+
23
+ # Map the name of the set to the ActiveRecord (or other) class name that
24
+ # will provide (at a minimum) the required oai_dc attributes/methods.
25
+ # E.g.
26
+ # config.sets = [
27
+ # {
28
+ # spec: 'class:party',
29
+ # name: 'Parties',
30
+ # model: Person
31
+ # },
32
+ # {
33
+ # spec: 'class:service',
34
+ # name: 'Services',
35
+ # description: 'Things that are services',
36
+ # model: Instrument
37
+ # }
38
+ # ]
39
+ config.sets = []
40
+
41
+ config.additional_formats = [
42
+ OAI::Provider::Metadata::RIFCS
43
+ ]
44
+
45
+ end
@@ -0,0 +1,34 @@
1
+ require 'oai'
2
+ require 'patched_record_response'
3
+ require 'ar_wrapper_model'
4
+ module OAIProvider
5
+
6
+ @@provider = nil
7
+
8
+ def self.provider
9
+ @@provider ||= build_set_provider
10
+ end
11
+
12
+ private
13
+
14
+ def self.build_set_provider
15
+
16
+ provider_name = "ARProvider"
17
+ provider_class = Object.const_set(provider_name, Class.new(OAI::Provider::Base))
18
+ provider_class.repository_name OaiRepository.repository_name
19
+ provider_class.repository_url OaiRepository.repository_url
20
+ provider_class.record_prefix OaiRepository.record_prefix
21
+ provider_class.admin_email OaiRepository.admin_email
22
+ provider_class.source_model ARWrapperModel.new(
23
+ sets: OaiRepository.sets,
24
+ limit: OaiRepository.limit,
25
+ timestamp_field: OaiRepository.timestamp_field
26
+ )
27
+ OaiRepository.additional_formats.each do |format|
28
+ provider_class.register_format(format.instance)
29
+ end
30
+ provider_class
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,5 @@
1
+ module OaiRepository
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace OaiRepository
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module OaiRepository
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,33 @@
1
+ require "oai_repository/engine"
2
+
3
+ module OaiRepository
4
+
5
+ def self.setup
6
+ yield self
7
+ end
8
+
9
+ mattr_accessor :repository_name
10
+ @@repository_name = 'Unspecified'
11
+
12
+ mattr_accessor :repository_url
13
+ @@repository_url = nil
14
+
15
+ mattr_accessor :record_prefix
16
+ @@record_prefix = 'PREFIX'
17
+
18
+ mattr_accessor :admin_email
19
+ @@admin_email = 'root@localhost'
20
+
21
+ mattr_accessor :sets
22
+ @@sets = {}
23
+
24
+ mattr_accessor :additional_formats
25
+ @@formats = []
26
+ ##OAI::Provider::Metadata::RIFCS.instance
27
+
28
+ mattr_accessor :limit
29
+ @@limit = 100
30
+
31
+ mattr_accessor :timestamp_field
32
+ @@timestamp_field = 'updated_at'
33
+ end
@@ -0,0 +1,16 @@
1
+ module OAI::Provider::Response
2
+ class RecordResponse < Base
3
+
4
+ private
5
+
6
+ def identifier_for(record)
7
+ if record.respond_to?(:oai_dc_identifier)
8
+ record.oai_dc_identifier
9
+ else
10
+ "#{provider.prefix}/#{record.id}"
11
+ end
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,22 @@
1
+ require 'oai'
2
+ module OAI::Provider::Metadata
3
+
4
+ class RIFCS < Format
5
+ def initialize
6
+ @prefix = 'rif'
7
+ @schema = 'http://services.ands.org.au/documentation/rifcs/1.3/schema/registryObjects.xsd'
8
+ @namespace = 'http://ands.org.au/standards/rif-cs/registryObjects'
9
+ @element_namespace = 'rif-cs'
10
+ end
11
+
12
+ def header_specification
13
+ {
14
+ 'xmlns' => "http://ands.org.au/standards/rif-cs/registryObjects" ,
15
+ 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance" ,
16
+ 'xsi:schemaLocation' => "http://ands.org.au/standards/rif-cs/registryObjects http://services.ands.org.au/documentation/rifcs/1.3/schema/registryObjects.xsd"
17
+ }
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :oai_repository do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oai_repository
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sean McCarthy
9
+ - Diego Alonso de Marcos
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-07-16 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ requirement: &77154580 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '3.1'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *77154580
26
+ - !ruby/object:Gem::Dependency
27
+ name: oai
28
+ requirement: &77154060 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *77154060
37
+ - !ruby/object:Gem::Dependency
38
+ name: sqlite3
39
+ requirement: &77153150 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *77153150
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec-rails
50
+ requirement: &77202670 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *77202670
59
+ - !ruby/object:Gem::Dependency
60
+ name: capybara
61
+ requirement: &77200990 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *77200990
70
+ description: An Engine for Rails (3.1+) that allows you to make your application an
71
+ OAI-PMH Data Provider. See http://www.openarchives.org/pmh/ and http://www.openarchives.org/OAI/openarchivesprotocol.html#Repository
72
+ email:
73
+ - sean@intersect.org.au
74
+ - diego@intersect.org.au
75
+ executables: []
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - app/controllers/oai_repository/application_controller.rb
80
+ - app/controllers/oai_repository/services_controller.rb
81
+ - app/assets/stylesheets/oai_repository/services.css
82
+ - app/assets/stylesheets/oai_repository/application.css
83
+ - app/assets/javascripts/oai_repository/application.js
84
+ - app/assets/javascripts/oai_repository/services.js
85
+ - app/helpers/oai_repository/services_helper.rb
86
+ - app/helpers/oai_repository/application_helper.rb
87
+ - app/views/oai_repository/services/show.html.erb
88
+ - app/views/layouts/oai_repository/application.html.erb
89
+ - config/routes.rb
90
+ - lib/patched_record_response.rb
91
+ - lib/rifcs_format.rb
92
+ - lib/oai_repository.rb
93
+ - lib/oai_repository/engine.rb
94
+ - lib/oai_repository/version.rb
95
+ - lib/tasks/oai_repository_tasks.rake
96
+ - lib/oai_provider.rb
97
+ - lib/generators/oai_repository/install_generator.rb
98
+ - lib/generators/templates/oai_repository.rb
99
+ - lib/ar_wrapper_model.rb
100
+ - MIT-LICENSE
101
+ - Rakefile
102
+ - README.rdoc
103
+ homepage: https://github.com/IntersectAustralia/oai_repository
104
+ licenses: []
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ segments:
116
+ - 0
117
+ hash: -625295055
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ segments:
125
+ - 0
126
+ hash: -625295055
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 1.8.15
130
+ signing_key:
131
+ specification_version: 3
132
+ summary: A Rails (3.1+) Engine to provide an OAI repository
133
+ test_files: []