iqvoc 4.0.3 → 4.0.4

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.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 4.0.4
2
+
3
+ * Triplestore Sync
4
+ * Bugfixes
5
+
1
6
  ## 4.0.3
2
7
 
3
8
  * New frontpage with quick search
data/Gemfile CHANGED
@@ -30,7 +30,8 @@ end
30
30
  gem 'kaminari'
31
31
  gem 'authlogic'
32
32
  gem 'cancan'
33
- gem 'iq_rdf', '~> 0.1.2'
33
+ gem 'iq_rdf'
34
+ gem 'iq_triplestorage'
34
35
  gem 'json'
35
36
  gem 'rails_autolink'
36
37
  gem 'jruby-openssl', :platforms => :jruby
@@ -67,6 +68,7 @@ group :test do
67
68
  gem 'spork-testunit'
68
69
  gem 'turn'
69
70
  gem 'minitest'
71
+ gem 'webmock'
70
72
  end
71
73
 
72
74
  group :production do
data/Gemfile.lock CHANGED
@@ -60,6 +60,7 @@ GEM
60
60
  json
61
61
  childprocess (0.3.5)
62
62
  ffi (~> 1.0, >= 1.0.6)
63
+ crack (0.3.1)
63
64
  database_cleaner (0.8.0)
64
65
  erubis (2.7.0)
65
66
  excon (0.16.2)
@@ -83,9 +84,11 @@ GEM
83
84
  excon (~> 0.16.1)
84
85
  hike (1.2.1)
85
86
  i18n (0.6.1)
86
- iq_rdf (0.1.5)
87
+ iq_rdf (0.1.8)
87
88
  builder
88
89
  bundler
90
+ iq_triplestorage (0.1.2)
91
+ typhoeus
89
92
  jdbc-mysql (5.1.13)
90
93
  jdbc-sqlite3 (3.7.2)
91
94
  journey (1.0.4)
@@ -179,12 +182,18 @@ GEM
179
182
  polyglot (>= 0.3.1)
180
183
  turn (0.9.6)
181
184
  ansi
185
+ typhoeus (0.4.2)
186
+ ffi (~> 1.0)
187
+ mime-types (~> 1.18)
182
188
  tzinfo (0.3.33)
183
189
  uglifier (1.3.0)
184
190
  execjs (>= 0.3.0)
185
191
  multi_json (~> 1.0, >= 1.0.2)
186
192
  view_marker (1.0.0)
187
193
  rails
194
+ webmock (1.8.11)
195
+ addressable (>= 2.2.7)
196
+ crack (>= 0.1.7)
188
197
  xpath (0.1.4)
189
198
  nokogiri (~> 1.3)
190
199
 
@@ -206,7 +215,8 @@ DEPENDENCIES
206
215
  factory_girl_rails
207
216
  fastercsv
208
217
  heroku
209
- iq_rdf (~> 0.1.2)
218
+ iq_rdf
219
+ iq_triplestorage
210
220
  jruby-openssl
211
221
  json
212
222
  kaminari
@@ -225,3 +235,4 @@ DEPENDENCIES
225
235
  turn
226
236
  uglifier (>= 1.0.3)
227
237
  view_marker
238
+ webmock
@@ -14,7 +14,10 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
+ require 'iqvoc/rdf_sync'
18
+
17
19
  class Concepts::VersionsController < ApplicationController
20
+ include Iqvoc::RDFSync::Helper
18
21
 
19
22
  def merge
20
23
  current_concept = Iqvoc::Concept.base_class.by_origin(params[:origin]).published.last
@@ -25,13 +28,17 @@ class Concepts::VersionsController < ApplicationController
25
28
 
26
29
  ActiveRecord::Base.transaction do
27
30
  if current_concept.blank? || current_concept.destroy
31
+ new_version.rdf_updated_at = nil
28
32
  new_version.publish
29
33
  new_version.unlock
30
34
  if new_version.valid_with_full_validation?
31
35
  new_version.save
32
- if RdfStore.update(rdf_url(:id => new_version, :format => :ttl), concept_url(:id => new_version, :format => :ttl))
33
- new_version.update_attribute(:rdf_updated_at, 1.seconds.since)
36
+
37
+ if Iqvoc.config["triplestore.autosync"]
38
+ synced = triplestore_syncer.sync([new_version]) # XXX: blocking
39
+ flash[:warning] = "triplestore synchronization failed" unless synced # TODO: i18n
34
40
  end
41
+
35
42
  flash[:success] = t("txt.controllers.versioning.published")
36
43
  redirect_to concept_path(:id => new_version)
37
44
  else
@@ -27,4 +27,25 @@ class RdfController < ApplicationController
27
27
  end
28
28
  end
29
29
 
30
+ def show
31
+ scope = if params[:published] == "0"
32
+ Iqvoc::Concept.base_class.unpublished
33
+ else
34
+ Iqvoc::Concept.base_class.published
35
+ end
36
+ if @concept = scope.by_origin(params[:id]).with_associations.last
37
+ respond_to do |format|
38
+ format.html do
39
+ redirect_to concept_url(:id => @concept.origin, :published => params[:published])
40
+ end
41
+ format.any do
42
+ authorize! :read, @concept
43
+ render "concepts/show"
44
+ end
45
+ end
46
+ else
47
+ raise ActiveRecord::RecordNotFound.new("Concept '#{params[:id]}' not found.")
48
+ end
49
+ end
50
+
30
51
  end
@@ -0,0 +1,61 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright 2011 innoQ Deutschland GmbH
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
+ require 'iqvoc/rdf_sync'
18
+
19
+ class TriplestoreSyncController < ApplicationController
20
+ include Iqvoc::RDFSync::Helper
21
+
22
+ def index
23
+ authorize! :use, :dashboard
24
+
25
+ if Iqvoc.config["triplestore.url"] == Iqvoc.config.defaults["triplestore.url"]
26
+ flash.now[:warning] = I18n.t("txt.controllers.triplestore_sync.config_warning")
27
+ else
28
+ host = Iqvoc.config["triplestore.url"]
29
+ username = Iqvoc.config["triplestore.username"].presence
30
+ password = Iqvoc.config["triplestore.password"].presence
31
+ target_info = host
32
+ if username && password
33
+ target_info = "#{target_info} (as #{username} with password)" # XXX: i18n
34
+ elsif username
35
+ target_info = "#{target_info} (as #{username})" # XXX: i18n
36
+ end
37
+ flash.now[:info] = I18n.t("txt.controllers.triplestore_sync.config_info",
38
+ :target_info => target_info)
39
+ end
40
+
41
+ # per-class pagination
42
+ @candidates = Iqvoc::RDFSync.candidates.map do |records|
43
+ records.page(params[:page])
44
+ end
45
+ end
46
+
47
+ def sync
48
+ authorize! :use, :dashboard
49
+
50
+ success = triplestore_syncer.all # XXX: long-running
51
+
52
+ if success
53
+ flash[:success] = I18n.t("txt.controllers.triplestore_sync.success")
54
+ else
55
+ flash[:error] = I18n.t("txt.controllers.triplestore_sync.error")
56
+ end
57
+
58
+ redirect_to :action => "index"
59
+ end
60
+
61
+ end
@@ -11,4 +11,6 @@
11
11
  <%= sidebar_header 'Extras' %>
12
12
  <%= sidebar_item(:icon => :upload, :text => t('txt.views.dashboard.import'),
13
13
  :path => import_url, :active => params[:controller] == 'import', :perms => [:import, Concept::Base]) %>
14
+ <%= sidebar_item(:icon => :repeat, :text => t('txt.views.triplestore_sync.caption'),
15
+ :path => triplestore_sync_path, :active => params[:controller] == "triplestore_sync") %>
14
16
  <% end %>
@@ -0,0 +1,24 @@
1
+ <%= page_header :title => t("txt.views.triplestore_sync.caption") %>
2
+ <%= render "dashboard/sidebar" %>
3
+
4
+ <%= form_tag triplestore_sync_path(:format => nil, :lang => nil), :method => :post do %>
5
+ <%= submit_tag t("txt.views.triplestore_sync.start"), :class => "btn btn-primary" %>
6
+ <% end %>
7
+
8
+ <% @candidates.each do |records| %>
9
+ <% klass = records.table.engine %>
10
+ <h3><%= klass.model_name.human(:count => 2) %></h3>
11
+ <ul>
12
+ <% records.each do |record| %>
13
+ <% # XXX: does not belong here -- also heavily over-engineered
14
+ doc = IqRdf::Document.new(root_url(:lang => nil))
15
+ Iqvoc.default_rdf_namespace_helper_methods.each do |meth|
16
+ doc.namespaces(self.send(meth))
17
+ end
18
+ uri = record.build_rdf_subject(doc, controller).full_uri # XXX: we actually just want path, plus HTML format
19
+ %>
20
+ <li><%= link_to record.to_s, uri %></li>
21
+ <% end %>
22
+ </ul>
23
+ <%= paginate records %>
24
+ <% end %>
@@ -68,27 +68,5 @@ module Iqvoc
68
68
 
69
69
  # Version of your assets, change this if you want to expire all your assets
70
70
  config.assets.version = '1.0'
71
-
72
- # TODO This must be refactored!
73
-
74
- # The JDBC driver url for the coinnection to the virtuoso triple store.
75
- # Login crdentials have to be stored here too. See
76
- # http://docs.openlinksw.com/virtuoso/VirtuosoDriverJDBC.html#jdbcurl4mat for
77
- # more details.
78
- # Example: "jdbc:virtuoso://localhost:1111/UID=dba/PWD=dba"
79
- # Use nil to disable virtuoso triple synchronization
80
- config.virtuoso_jdbc_driver_url = nil
81
-
82
- # Set up the virtuoso synchronization (which is a triggered pull from the
83
- # virtuoso server) to be run in a new thread.
84
- # This is needed in environments where the webserver only runs in a single
85
- # process/thread (mostly in development environments).
86
- # When a synchronizaion would be triggered e.g. from a running
87
- # update action in the UPB, the update would trigger virtuoso to do a HTTP GET
88
- # back to the UPB to fetch the RDF data. But the only process in the UPB would be
89
- # blocked by the update... => Deadlock. You can avoid this by using the threaded
90
- # mode.
91
- config.virtuoso_sync_threaded = false
92
-
93
71
  end
94
72
  end
@@ -92,6 +92,10 @@ de:
92
92
  available_languages: verfügbare Sprachen
93
93
  languages_pref_labeling: Sprachen für bevorzugte Labels
94
94
  languages_further_labelings_Labeling::SKOS::AltLabel: Sprachen für alternative Labels
95
+ triplestore_url: Triplestore-URL
96
+ triplestore_username: Benutzername für Triplestore
97
+ triplestore_password: Passwort für Triplestore
98
+ triplestore_autosync: automatische Synchronisation mit Triplestore
95
99
  alphabetical_concepts:
96
100
  untranslated_concepts:
97
101
  caption: "Fehlende Übersetzungen"
@@ -242,6 +246,9 @@ de:
242
246
  import: Import
243
247
  collections:
244
248
  new: "Neue Kollektion"
249
+ triplestore_sync:
250
+ caption: "Sync mit Triplestore"
251
+ start: "Synchronisierung starten"
245
252
 
246
253
  partials:
247
254
  note:
@@ -289,7 +296,6 @@ de:
289
296
  consistency_check_error: "Instanz ist inkonsistent!"
290
297
  to_review_success: "Wurde in den Freigabeprozess aufgenommen"
291
298
  to_review_error: "Konnte nicht in den Freigabeprozess aufgenommen werden"
292
- virtuoso_exception: "Das Objekt wurde veröffentlicht, konnte aber nicht mit Virtuoso synchronisiert werden. Fehler: "
293
299
  labelings:
294
300
  save:
295
301
  success: "Das Labeling wurde erfolgreich gespeichert."
@@ -318,8 +324,6 @@ de:
318
324
  label_error: "Ungültige Label-Zuordnungen:\n%s"
319
325
  narrowers:
320
326
  error: "Die Assoziation wurde nicht gefunden. Evtl. wurde diese von der anderen Seite bearbeitet. Bitte das Fenster neu laden!"
321
- triple_store_syncs:
322
- success: "Es wurden %{concept_count} freigegebene Concepts und %{label_count} freigegebene Labels synchronisiert."
323
327
  collections:
324
328
  save:
325
329
  success: "Die Kollektion wurde erfolgreich gespeichert."
@@ -328,6 +332,11 @@ de:
328
332
  success: "Die Kollektion wurde erfolgreich gelöscht."
329
333
  error: "Die Kollektion konnte nicht gelöscht werden."
330
334
  circular_error: "Ungültige Subkollektions-Zuordnungen aufgrund von Zirkelbezügen:\n%s"
335
+ triplestore_sync:
336
+ success: "Synchronisierung erfolgreich."
337
+ error: "Bei der Synchronisierung traten Fehler auf - einige Einträge wurden nicht synchronisiert."
338
+ config_info: "Synchronisierung mit Triplestore: %{target_info}"
339
+ config_warning: "Bisher wurde kein Triplestore konfiguriert."
331
340
 
332
341
  models:
333
342
  label:
@@ -92,6 +92,10 @@ en:
92
92
  available_languages: available languages
93
93
  languages_pref_labeling: languages for preferred labels
94
94
  languages_further_labelings_Labeling::SKOS::AltLabel: languages for alternative labels
95
+ triplestore_url: triplestore URL
96
+ triplestore_username: username for triplestore
97
+ triplestore_password: password for triplestore
98
+ triplestore_autosync: automatic triplestore synchronization
95
99
  alphabetical_concepts:
96
100
  untranslated_concepts:
97
101
  caption: "Missing Translations"
@@ -249,6 +253,9 @@ en:
249
253
  import: Import
250
254
  collections:
251
255
  new: "New collection"
256
+ triplestore_sync:
257
+ caption: "Sync with Triplestore"
258
+ start: "Start synchronization"
252
259
 
253
260
  partials:
254
261
  note:
@@ -296,7 +303,6 @@ en:
296
303
  consistency_check_success: "Instance is consistent."
297
304
  consistency_check_success_with_warning: "Instance is consistent but has no relations."
298
305
  consistency_check_error: "Instance is inconsistent."
299
- virtuoso_exception: "The object has been published, but could not by synced with Virtuoso. Error: "
300
306
  labelings:
301
307
  save:
302
308
  success: "The labeling has been saved."
@@ -325,8 +331,6 @@ en:
325
331
  label_error: "Invalid label assignments:\n%s"
326
332
  narrowers:
327
333
  error: "Relation not found. Probably removed from the target side. Please refresh your browser window."
328
- triple_store_syncs:
329
- success: "%{concept_count} published concepts and %{label_count} published labels have been synced."
330
334
  collections:
331
335
  save:
332
336
  success: "The collection has been successfully created."
@@ -335,6 +339,11 @@ en:
335
339
  success: "The collection has been successfully deleted."
336
340
  error: "The collection could not be deleted."
337
341
  circular_error: "Invalid sub-collection assignments due to circular references:\n%s"
342
+ triplestore_sync:
343
+ success: "Synchronization successful."
344
+ error: "An error occurred during the synchronization process - some entities have not been synchronized."
345
+ config_info: "Synchronizing with triplestore: %{target_info}"
346
+ config_warning: "Triplestore has not been configured yet."
338
347
 
339
348
  models:
340
349
  label:
data/config/routes.rb CHANGED
@@ -28,8 +28,6 @@ Rails.application.routes.draw do
28
28
  resources :concepts
29
29
  resources :collections
30
30
 
31
- resources :triple_store_syncs, :only => [:new, :create]
32
-
33
31
  match "/" => "frontpage#index"
34
32
 
35
33
  match "concepts/:origin/branch(.:format)" => "concepts/versions#branch", :as => "concept_versions_branch"
@@ -58,8 +56,11 @@ Rails.application.routes.draw do
58
56
  get "help" => "pages#help", :as => "help"
59
57
 
60
58
  root :to => 'frontpage#index', :format => nil
59
+
60
+ get "triplestore_sync" => "triplestore_sync#index"
61
+ post "triplestore_sync" => "triplestore_sync#sync"
61
62
  end
62
63
 
63
64
  match '/scheme(.:format)' => 'rdf#scheme', :as => 'scheme'
64
- match '/:id(.:format)' => 'concepts#show', :as => 'rdf'
65
+ match '/:id(.:format)' => 'rdf#show', :as => 'rdf'
65
66
  end
data/lib/iqvoc.rb CHANGED
@@ -20,6 +20,7 @@ require 'iqvoc/configuration/core'
20
20
  require 'iqvoc/configuration/concept'
21
21
  require 'iqvoc/configuration/collection'
22
22
  require 'iqvoc/configuration/label'
23
+ require 'iqvoc/configuration/sync'
23
24
 
24
25
  module Iqvoc
25
26
  unless Iqvoc.const_defined?(:Application)
@@ -40,6 +41,10 @@ module Iqvoc
40
41
  include Iqvoc::Configuration::Label
41
42
  end
42
43
 
44
+ module Sync
45
+ include Iqvoc::Configuration::Sync
46
+ end
47
+
43
48
  end
44
49
 
45
50
  # FIXME: For reasons yet unknown, the load hook is executed twice
@@ -0,0 +1,28 @@
1
+ require 'active_support/concern'
2
+
3
+ module Iqvoc
4
+ module Configuration
5
+ module Sync
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ mattr_accessor :syncable_class_names
10
+ self.syncable_class_names = [Iqvoc::Concept.base_class_name]
11
+
12
+ Iqvoc.config.register_settings({
13
+ "triplestore.url" => "http://example.org:8080",
14
+ "triplestore.username" => "",
15
+ "triplestore.password" => "",
16
+ "triplestore.autosync" => false
17
+ })
18
+ end
19
+
20
+ module ClassMethods
21
+ def syncable_classes
22
+ self.syncable_class_names.map { |name| name.constantize }
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -82,26 +82,6 @@ module Iqvoc::Environments
82
82
 
83
83
  # The default URI prefix for RDF data. This will be followed by a document
84
84
  # specific shnippet like (specimenType) and the id.
85
-
86
- # The JDBC driver URL for the connection to the virtuoso triple store.
87
- # Login credentials have to be stored here too. See
88
- # http://docs.openlinksw.com/virtuoso/VirtuosoDriverJDBC.html#jdbcurl4mat for
89
- # more details.
90
- # Example: "jdbc:virtuoso://localhost:1111/UID=dba/PWD=dba"
91
- # Use nil to disable virtuoso triple synchronization
92
- # Rails.application.config.virtuoso_jdbc_driver_url = "jdbc:virtuoso://virtuoso.dyndns.org:1111/UID=iqvoc/PWD=vocpass!/charset=UTF-8"
93
- config.virtuoso_jdbc_driver_url = nil
94
-
95
- # Set up the virtuoso synchronization (which is a triggered pull from the
96
- # virtuoso server) to be run in a new thread.
97
- # This is needed in environments where the web server only runs in a single
98
- # process/thread (mostly in development environments).
99
- # When a synchronization would be triggered e.g. from a running
100
- # update action in the UPB, the update would trigger virtuoso to do a HTTP GET
101
- # back to the UPB to fetch the RDF data. But the only process in the UPB would be
102
- # blocked by the update... => Deadlock. You can avoid this by using the threaded
103
- # mode.
104
- config.virtuoso_sync_threaded = false
105
85
  end
106
86
 
107
87
  end
@@ -67,26 +67,6 @@ module Iqvoc::Environments
67
67
  # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
68
68
  # parameters by using an attr_accessible or attr_protected declaration.
69
69
  config.active_record.whitelist_attributes = false
70
-
71
- # The JDBC driver URL for the connection to the virtuoso triple store.
72
- # Login credentials have to be stored here too. See
73
- # http://docs.openlinksw.com/virtuoso/VirtuosoDriverJDBC.html#jdbcurl4mat for
74
- # more details.
75
- # Example: "jdbc:virtuoso://localhost:1111/UID=dba/PWD=dba"
76
- # Use nil to disable virtuoso triple synchronization
77
- # Rails.application.config.virtuoso_jdbc_driver_url = "jdbc:virtuoso://virtuoso.dyndns.org:1111/UID=iqvoc/PWD=vocpass!/charset=UTF-8"
78
- config.virtuoso_jdbc_driver_url = nil
79
-
80
- # Set up the virtuoso synchronization (which is a triggered pull from the
81
- # virtuoso server) to be run in a new thread.
82
- # This is needed in environments where the web server only runs in a single
83
- # process/thread (mostly in development environments).
84
- # When a synchronization would be triggered e.g. from a running
85
- # update action in the UPB, the update would trigger virtuoso to do a HTTP GET
86
- # back to the UPB to fetch the RDF data. But the only process in the UPB would be
87
- # blocked by the update... => Deadlock. You can avoid this by using the threaded
88
- # mode.
89
- config.virtuoso_sync_threaded = false
90
70
  end
91
71
 
92
72
  end
@@ -0,0 +1,111 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'iq_triplestorage/virtuoso_adaptor'
4
+
5
+ class Iqvoc::RDFSync
6
+ delegate :url_helpers, :to => "Rails.application.routes"
7
+
8
+ def initialize(base_url, target_host, *args)
9
+ @base_url = base_url
10
+ @target_host = target_host
11
+ options = args.extract_options!
12
+ @target_port = options[:port]
13
+ @username = options[:username]
14
+ @password = options[:password]
15
+ @batch_size = options[:batch_size]
16
+ @view_context = options[:view_context] # XXX: not actually optional
17
+ raise(ArgumentError, "missing view context") unless @view_context # XXX: smell (see above)
18
+ end
19
+
20
+ def all # TODO: rename
21
+ timestamp = Time.now
22
+ errors = false
23
+
24
+ gather_candidates do |records|
25
+ success = sync(records, timestamp)
26
+ errors = true unless success
27
+ end
28
+
29
+ return !errors
30
+ end
31
+
32
+ def sync(records, timestamp=nil)
33
+ timestamp ||= Time.now
34
+
35
+ success = push(records)
36
+ if success
37
+ records.each do |record|
38
+ record.update_attribute(:rdf_updated_at, timestamp)
39
+ end
40
+ end
41
+
42
+ return success
43
+ end
44
+
45
+ def push(records)
46
+ data = records.inject({}) do |memo, record|
47
+ graph_uri = url_helpers.rdf_url(record.origin,
48
+ :host => URI.parse(@base_url).host, :format => nil, :lang => nil)
49
+ memo[graph_uri] = serialize(record)
50
+ memo
51
+ end
52
+
53
+ adaptor = IqTriplestorage::VirtuosoAdaptor.new(@target_host, @target_port,
54
+ @username, @password)
55
+ return adaptor.batch_update(data)
56
+ end
57
+
58
+ def serialize(record)
59
+ # while this method is really fugly, iQvoc essentially requires us to mock a
60
+ # view in order to get to the RDF serialization
61
+
62
+ doc = IqRdf::Document.new(@base_url)
63
+ Iqvoc.default_rdf_namespace_helper_methods.each do |meth|
64
+ doc.namespaces(@view_context.send(meth))
65
+ end
66
+
67
+ rdf_helper = Object.new.extend(RdfHelper)
68
+ if record.is_a? Iqvoc::Concept.base_class
69
+ rdf_helper.render_concept(doc, record)
70
+ else # XXX: must be extensible
71
+ raise NotImplementedError, "unable to render RDF for #{record.class}"
72
+ end
73
+
74
+ return doc.to_ntriples
75
+ end
76
+
77
+ # yields batches of candidates for synchronization
78
+ def gather_candidates # TODO: rename
79
+ Iqvoc::Sync.syncable_classes.each do |klass|
80
+ self.class.candidates(klass).find_in_batches(:batch_size => @batch_size) do |records|
81
+ yield records
82
+ end
83
+ end
84
+ end
85
+
86
+ # returns one or multiple ActiveRecord::RelationS, depending on whether
87
+ # `klass` is provided
88
+ def self.candidates(klass=nil)
89
+ return klass ? klass.published.unsynced :
90
+ Iqvoc::Sync.syncable_classes.map { |klass| candidates(klass) }
91
+ end
92
+
93
+ end
94
+
95
+ module Iqvoc::RDFSync::Helper # TODO: rename -- XXX: does not belong here!?
96
+
97
+ def triplestore_syncer
98
+ base_url = root_url(:lang => nil) # XXX: brittle in the face of future changes?
99
+
100
+ host = URI.parse(Iqvoc.config["triplestore.url"])
101
+ port = host.port
102
+ host.port = 80 # XXX: hack to remove port from serialization
103
+ host = host.to_s
104
+
105
+ return Iqvoc::RDFSync.new(base_url, host, :port => port,
106
+ :username => Iqvoc.config["triplestore.username"].presence,
107
+ :password => Iqvoc.config["triplestore.password"].presence,
108
+ :view_context => view_context) # fugly, but necessary; cf. RDFSync#serialize
109
+ end
110
+
111
+ end
data/lib/iqvoc/version.rb CHANGED
@@ -15,5 +15,5 @@
15
15
  # limitations under the License.
16
16
 
17
17
  module Iqvoc
18
- VERSION = "4.0.3"
18
+ VERSION = "4.0.4"
19
19
  end
data/test/test_helper.rb CHANGED
@@ -21,6 +21,8 @@ Spork.prefork do
21
21
  ENV["RAILS_ENV"] = "test"
22
22
  require File.expand_path('../../config/environment', __FILE__)
23
23
  require 'rails/test_help'
24
+ require 'webmock'
25
+ WebMock.allow_net_connect! # required for integration tests
24
26
  end
25
27
 
26
28
  Spork.each_run do
@@ -0,0 +1,123 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright 2011 innoQ Deutschland GmbH
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
+ require File.join(File.expand_path(File.dirname(__FILE__)), '../test_helper')
18
+
19
+ require 'iqvoc/rdf_sync'
20
+
21
+ class RDFSyncTest < ActiveSupport::TestCase
22
+
23
+ setup do
24
+ @rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
25
+ @skos = "http://www.w3.org/2004/02/skos/core#"
26
+
27
+ @base_url = "http://example.com/"
28
+ @target_host = "http://example.org"
29
+ @username = "johndoe"
30
+
31
+ class FakeViewContext # XXX: does not belong here
32
+ def iqvoc_default_rdf_namespaces
33
+ return Iqvoc.rdf_namespaces
34
+ end
35
+ end
36
+ @view_context = FakeViewContext.new
37
+
38
+ @sync = Iqvoc::RDFSync.new(@base_url, @target_host, :username => @username,
39
+ :view_context => @view_context)
40
+
41
+ @concepts = 15.times.map do
42
+ FactoryGirl.create(:concept, :narrower_relations => [])
43
+ end
44
+
45
+ # HTTP request mocking
46
+ @observers = [] # one per request
47
+ WebMock.disable_net_connect!
48
+ WebMock.stub_request(:any, /.*example.org.*/).with do |req|
49
+ # not using WebMock's custom assertions as those didn't seem to provide
50
+ # sufficient flexibility
51
+ fn = @observers.shift
52
+ raise(TypeError, "missing request observer: #{req.inspect}") unless fn
53
+ fn.call(req)
54
+ true
55
+ end.to_return do |req|
56
+ { :status => 201 }
57
+ end
58
+ end
59
+
60
+ teardown do
61
+ WebMock.reset!
62
+ WebMock.allow_net_connect!
63
+ raise(TypeError, "unhandled request observer") unless @observers.length == 0
64
+ end
65
+
66
+ test "serialization" do
67
+ concept = @concepts[0]
68
+
69
+ assert @sync.serialize(concept).include?(<<-EOS.strip)
70
+ <#{@base_url}#{concept.origin}> <#{@rdf}type> <#{@skos}Concept> .
71
+ EOS
72
+ end
73
+
74
+ test "single-batch syncronization" do
75
+ concepts = @concepts[0..4]
76
+
77
+ @observers << lambda do |req|
78
+ concepts.each do |concept|
79
+ uri = @base_url + concept.origin
80
+ assert req.body.include?("CLEAR GRAPH <#{uri}>")
81
+ end
82
+ end
83
+ @observers << lambda do |req|
84
+ concepts.each do |concept|
85
+ path = req.uri.path
86
+ assert path.start_with?("/DAV/home/#{@username}/")
87
+ assert_equal 4, path.count("/")
88
+ uri = @base_url + concept.origin
89
+ assert req.body.include?("INSERT IN GRAPH <#{uri}> {")
90
+ end
91
+ end
92
+ res = @sync.sync(concepts)
93
+ end
94
+
95
+ test "full synchronization" do
96
+ concepts = Iqvoc::Concept.base_class.published.unsynced
97
+
98
+ assert_not_equal 0, concepts.count
99
+ assert_not_equal 0, concepts.where(:rdf_updated_at => nil).count
100
+
101
+ 2.times do # 3 * 2 requests (reset + insert per batch)
102
+ @observers << lambda { |req| } # no need to check details here
103
+ end
104
+ assert @sync.all
105
+ assert_equal 0, concepts.where(:rdf_updated_at => nil).count
106
+ end
107
+
108
+ test "request batches" do
109
+ concepts = Iqvoc::Concept.base_class.published.unsynced
110
+ concept_count = concepts.count
111
+ batch_count = 3
112
+
113
+ sync = Iqvoc::RDFSync.new(@base_url, @target_host, :username => @username,
114
+ :batch_size => (concept_count / batch_count).ceil,
115
+ :view_context => @view_context)
116
+
117
+ (batch_count * 2).times do # two requests (reset + insert) per batch
118
+ @observers << lambda { |req| } # no need to check details here
119
+ end
120
+ assert sync.all
121
+ end
122
+
123
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iqvoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.3
4
+ version: 4.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,11 +11,11 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2012-11-02 00:00:00.000000000 Z
14
+ date: 2012-11-05 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rails
18
- requirement: &70342874186600 !ruby/object:Gem::Requirement
18
+ requirement: &70162871907980 !ruby/object:Gem::Requirement
19
19
  none: false
20
20
  requirements:
21
21
  - - ~>
@@ -23,10 +23,10 @@ dependencies:
23
23
  version: 3.2.1
24
24
  type: :runtime
25
25
  prerelease: false
26
- version_requirements: *70342874186600
26
+ version_requirements: *70162871907980
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
- requirement: &70342874186200 !ruby/object:Gem::Requirement
29
+ requirement: &70162871905160 !ruby/object:Gem::Requirement
30
30
  none: false
31
31
  requirements:
32
32
  - - ! '>='
@@ -34,10 +34,10 @@ dependencies:
34
34
  version: '0'
35
35
  type: :runtime
36
36
  prerelease: false
37
- version_requirements: *70342874186200
37
+ version_requirements: *70162871905160
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: kaminari
40
- requirement: &70342874185740 !ruby/object:Gem::Requirement
40
+ requirement: &70162871915900 !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
43
  - - ! '>='
@@ -45,10 +45,10 @@ dependencies:
45
45
  version: '0'
46
46
  type: :runtime
47
47
  prerelease: false
48
- version_requirements: *70342874185740
48
+ version_requirements: *70162871915900
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: authlogic
51
- requirement: &70342874185320 !ruby/object:Gem::Requirement
51
+ requirement: &70162871913820 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
54
  - - ! '>='
@@ -56,10 +56,10 @@ dependencies:
56
56
  version: '0'
57
57
  type: :runtime
58
58
  prerelease: false
59
- version_requirements: *70342874185320
59
+ version_requirements: *70162871913820
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: cancan
62
- requirement: &70342874184900 !ruby/object:Gem::Requirement
62
+ requirement: &70162871911780 !ruby/object:Gem::Requirement
63
63
  none: false
64
64
  requirements:
65
65
  - - ! '>='
@@ -67,10 +67,10 @@ dependencies:
67
67
  version: '0'
68
68
  type: :runtime
69
69
  prerelease: false
70
- version_requirements: *70342874184900
70
+ version_requirements: *70162871911780
71
71
  - !ruby/object:Gem::Dependency
72
72
  name: iq_rdf
73
- requirement: &70342874184400 !ruby/object:Gem::Requirement
73
+ requirement: &70162871922380 !ruby/object:Gem::Requirement
74
74
  none: false
75
75
  requirements:
76
76
  - - ~>
@@ -78,10 +78,10 @@ dependencies:
78
78
  version: 0.1.2
79
79
  type: :runtime
80
80
  prerelease: false
81
- version_requirements: *70342874184400
81
+ version_requirements: *70162871922380
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: json
84
- requirement: &70342874183980 !ruby/object:Gem::Requirement
84
+ requirement: &70162871933860 !ruby/object:Gem::Requirement
85
85
  none: false
86
86
  requirements:
87
87
  - - ! '>='
@@ -89,10 +89,10 @@ dependencies:
89
89
  version: '0'
90
90
  type: :runtime
91
91
  prerelease: false
92
- version_requirements: *70342874183980
92
+ version_requirements: *70162871933860
93
93
  - !ruby/object:Gem::Dependency
94
94
  name: rails_autolink
95
- requirement: &70342891454560 !ruby/object:Gem::Requirement
95
+ requirement: &70162871942060 !ruby/object:Gem::Requirement
96
96
  none: false
97
97
  requirements:
98
98
  - - ! '>='
@@ -100,10 +100,10 @@ dependencies:
100
100
  version: '0'
101
101
  type: :runtime
102
102
  prerelease: false
103
- version_requirements: *70342891454560
103
+ version_requirements: *70162871942060
104
104
  - !ruby/object:Gem::Dependency
105
105
  name: fastercsv
106
- requirement: &70342891454100 !ruby/object:Gem::Requirement
106
+ requirement: &70162871941320 !ruby/object:Gem::Requirement
107
107
  none: false
108
108
  requirements:
109
109
  - - ! '>='
@@ -111,10 +111,10 @@ dependencies:
111
111
  version: '0'
112
112
  type: :runtime
113
113
  prerelease: false
114
- version_requirements: *70342891454100
114
+ version_requirements: *70162871941320
115
115
  - !ruby/object:Gem::Dependency
116
116
  name: simple_form
117
- requirement: &70342891453680 !ruby/object:Gem::Requirement
117
+ requirement: &70162871940440 !ruby/object:Gem::Requirement
118
118
  none: false
119
119
  requirements:
120
120
  - - ! '>='
@@ -122,10 +122,10 @@ dependencies:
122
122
  version: '0'
123
123
  type: :runtime
124
124
  prerelease: false
125
- version_requirements: *70342891453680
125
+ version_requirements: *70162871940440
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: sass-rails
128
- requirement: &70342891453180 !ruby/object:Gem::Requirement
128
+ requirement: &70162871939660 !ruby/object:Gem::Requirement
129
129
  none: false
130
130
  requirements:
131
131
  - - ~>
@@ -133,7 +133,7 @@ dependencies:
133
133
  version: 3.2.5
134
134
  type: :runtime
135
135
  prerelease: false
136
- version_requirements: *70342891453180
136
+ version_requirements: *70162871939660
137
137
  description: iQvoc - a SKOS(-XL) vocabulary management system built on the Semantic
138
138
  Web
139
139
  email:
@@ -196,7 +196,7 @@ files:
196
196
  - app/controllers/pages_controller.rb
197
197
  - app/controllers/rdf_controller.rb
198
198
  - app/controllers/search_results_controller.rb
199
- - app/controllers/triple_store_syncs_controller.rb
199
+ - app/controllers/triplestore_sync_controller.rb
200
200
  - app/controllers/user_sessions_controller.rb
201
201
  - app/controllers/users_controller.rb
202
202
  - app/helpers/application_helper.rb
@@ -250,7 +250,6 @@ files:
250
250
  - app/models/note/skos/example.rb
251
251
  - app/models/note/skos/history_note.rb
252
252
  - app/models/note/skos/scope_note.rb
253
- - app/models/rdf_store.rb
254
253
  - app/models/search_extension.rb
255
254
  - app/models/user.rb
256
255
  - app/models/user_session.rb
@@ -328,7 +327,7 @@ files:
328
327
  - app/views/search_results/_sidebar.html.erb
329
328
  - app/views/search_results/index.html.erb
330
329
  - app/views/search_results/index.iqrdf
331
- - app/views/triple_store_syncs/new.html.erb
330
+ - app/views/triplestore_sync/index.html.erb
332
331
  - app/views/user_sessions/_form.html.erb
333
332
  - app/views/user_sessions/new.html.erb
334
333
  - app/views/users/_form.html.erb
@@ -410,6 +409,7 @@ files:
410
409
  - lib/iqvoc/configuration/concept.rb
411
410
  - lib/iqvoc/configuration/core.rb
412
411
  - lib/iqvoc/configuration/label.rb
412
+ - lib/iqvoc/configuration/sync.rb
413
413
  - lib/iqvoc/controller_extensions.rb
414
414
  - lib/iqvoc/deep_cloning.rb
415
415
  - lib/iqvoc/environments/development.rb
@@ -420,6 +420,7 @@ files:
420
420
  - lib/iqvoc/maker.rb
421
421
  - lib/iqvoc/origin.rb
422
422
  - lib/iqvoc/rankable.rb
423
+ - lib/iqvoc/rdf_sync.rb
423
424
  - lib/iqvoc/skos_importer.rb
424
425
  - lib/iqvoc/version.rb
425
426
  - lib/iqvoc/versioning.rb
@@ -452,6 +453,7 @@ files:
452
453
  - test/unit/instance_configuration_test.rb
453
454
  - test/unit/note_test.rb
454
455
  - test/unit/origin_test.rb
456
+ - test/unit/rdf_sync_test.rb
455
457
  - test/unit/skos_import_test.rb
456
458
  - vendor/assets/images/ajax-loader.gif
457
459
  - vendor/assets/images/bootstrap/glyphicons-halflings-white.png
@@ -548,4 +550,5 @@ test_files:
548
550
  - test/unit/instance_configuration_test.rb
549
551
  - test/unit/note_test.rb
550
552
  - test/unit/origin_test.rb
553
+ - test/unit/rdf_sync_test.rb
551
554
  - test/unit/skos_import_test.rb
@@ -1,45 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- # Copyright 2011 innoQ Deutschland GmbH
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
- class TripleStoreSyncsController < ApplicationController
18
-
19
- # TODO: This must definitely be rebuild!
20
-
21
- def new
22
- authorize! :use, :dashboard # TODO
23
- end
24
-
25
- def create
26
- authorize! :use, :dashboard # TODO
27
-
28
- time = Time.now
29
-
30
- rdf_helper = Object.new.extend(RdfHelper)
31
-
32
- Concept::Base.published.unsynced.find_each do |concept|
33
- concept.update_attribute(:rdf_updated_at, time) if RdfStore.mass_import(concept.rdf_uri, rdf_helper.render_ttl_for_concept(concept))
34
- end
35
-
36
- Label::Base.published.unsynced.find_each do |label|
37
- label.update_attribute(:rdf_updated_at, time) if RdfStore.mass_import(label.rdf_uri, rdf_helper.render_ttl_for_label(label))
38
- end
39
-
40
- flash.now[:notice] = I18n.t("txt.controllers.triple_store_syncs.success")
41
-
42
- render :action => "new"
43
- end
44
-
45
- end
@@ -1,96 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- # Copyright 2011 innoQ Deutschland GmbH
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
- class RdfStore
18
- # TODO This must be modularized and refactored and the thread should be
19
- # replaced by a direct rack call or usage of helpers
20
- #
21
- # * Modulization: The RdfStore should be more abstract. A special adapter
22
- # (e.g. for virtuoso or sesame) should be implemented in a extending
23
- # class.
24
- # * No threads: throw out the JRuby stuff and replace it with:
25
- # * Rack calls to the particluar action or
26
- # * With the help of the RdfHelper#render_concept method
27
-
28
- include Java rescue nil
29
- include java.lang.Runnable rescue nil
30
-
31
- @@conn = nil
32
-
33
- def initialize(graph_uri, ttl_url, do_delete, ttl_content = nil)
34
- if Rails.application.config.virtuoso_jdbc_driver_url
35
- @graph_uri = graph_uri
36
- @ttl_url = ttl_url
37
- @ttl_content = ttl_content
38
- @do_delete = do_delete
39
-
40
- Rails.logger.info("** [RdfStore] Beginning virtuoso sync. Insert turtle into graph <#{@graph_uri}> from url: #{@ttl_url}; Clear graph first: #{@do_delete}.")
41
-
42
- unless @@conn
43
- # import "virtuoso.jdbc3.Driver"
44
- java.lang.Class.forName("virtuoso.jdbc3.Driver", true, JRuby.runtime.jruby_class_loader)
45
- @@conn = java.sql.DriverManager.getConnection(Rails.application.config.virtuoso_jdbc_driver_url)
46
- Rails.logger.debug("** [RdfStore] JDBC connection is up.")
47
- end
48
-
49
- if Rails.application.config.virtuoso_sync_threaded
50
- Rails.logger.debug("** [RdfStore] Starting sync thread.")
51
- java.lang.Thread.new(self).start
52
- else
53
- self.run
54
- end
55
- end
56
- end
57
-
58
- def self.insert(graph_uri, ttl_url)
59
- return false unless Rails.application.config.virtuoso_jdbc_driver_url
60
- RdfStore.new(graph_uri, ttl_url, false)
61
- true
62
- end
63
-
64
- def self.update(graph_uri, ttl_url)
65
- return false unless Rails.application.config.virtuoso_jdbc_driver_url
66
- RdfStore.new(graph_uri, ttl_url, true)
67
- true
68
- end
69
-
70
- def self.delete(graph_uri)
71
- return false unless Rails.application.config.virtuoso_jdbc_driver_url
72
- RdfStore.new(graph_uri, nil, true)
73
- true
74
- end
75
-
76
- def self.mass_import(graph_uri, turtle_content)
77
- return false unless Rails.application.config.virtuoso_jdbc_driver_url
78
- RdfStore.new(graph_uri, nil, true, turtle_content)
79
- true
80
- end
81
-
82
- def run
83
- if (@do_delete)
84
- Rails.logger.debug("** [RdfStore] Executing SPARQL DELETE.")
85
- @@conn.createStatement().execute("SPARQL CLEAR GRAPH <#{@graph_uri}>")
86
- end
87
- if (@ttl_url)
88
- Rails.logger.debug("** [RdfStore] Executing turtle load from url.")
89
- @@conn.createStatement().execute("DB.DBA.TTLP(HTTP_GET('#{@ttl_url}'), '', '#{@graph_uri}')")
90
- end
91
- if (@ttl_content)
92
- @@conn.createStatement().execute("DB.DBA.TTLP('#{@ttl_content.gsub("\\", "\\\\\\").gsub("'", "\\\\'")}', '', '#{@graph_uri}')")
93
- end
94
- Rails.logger.info("** [RdfStore] Virtuoso sync done.")
95
- end
96
- end
@@ -1,7 +0,0 @@
1
- <%= flash[:notice] %>
2
-
3
- <h2>Triple Store Sync</h2>
4
-
5
- <%= form_tag triple_store_syncs_path, :method => :post do %>
6
- <%= submit_tag "Sync" %>
7
- <% end %>