iqvoc 4.0.3 → 4.0.4

Sign up to get free protection for your applications and to get access to all the features.
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 %>