Commit db219627f45001a8a4438229acd44114a3953856
Exists in
theme-brasil-digital-from-staging
and in
9 other branches
Merge branch 'virtuoso_integration' into stable
Showing
17 changed files
with
386 additions
and
0 deletions
Show diff stats
plugins/virtuoso/controllers/virtuoso_plugin_admin_controller.rb
0 → 100644
| @@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
| 1 | +class VirtuosoPluginAdminController < AdminController | ||
| 2 | + | ||
| 3 | + def index | ||
| 4 | + settings = params[:settings] | ||
| 5 | + settings ||= {} | ||
| 6 | + @settings = Noosfero::Plugin::Settings.new(environment, VirtuosoPlugin, settings) | ||
| 7 | + @harvest_running = VirtuosoPlugin::DspaceHarvest.new(environment).find_job.present? | ||
| 8 | + | ||
| 9 | + if request.post? | ||
| 10 | + @settings.save! | ||
| 11 | + session[:notice] = 'Settings successfully saved.' | ||
| 12 | + redirect_to :action => 'index' | ||
| 13 | + end | ||
| 14 | + end | ||
| 15 | + | ||
| 16 | + def force_harvest | ||
| 17 | + harvest = VirtuosoPlugin::DspaceHarvest.new(environment) | ||
| 18 | + harvest.start(params[:from_start]) | ||
| 19 | + session[:notice] = _('Harvest started') | ||
| 20 | + redirect_to :action => :index | ||
| 21 | + end | ||
| 22 | + | ||
| 23 | +end |
| @@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
| 1 | +class VirtuosoPlugin < Noosfero::Plugin | ||
| 2 | + | ||
| 3 | + def self.plugin_name | ||
| 4 | + "Virtuoso integration" | ||
| 5 | + end | ||
| 6 | + | ||
| 7 | + def self.plugin_description | ||
| 8 | + _('Virtuoso integration') | ||
| 9 | + end | ||
| 10 | + | ||
| 11 | + def content_types | ||
| 12 | + [VirtuosoPlugin::TriplesTemplate] | ||
| 13 | + end | ||
| 14 | + | ||
| 15 | + def settings | ||
| 16 | + @settings ||= Noosfero::Plugin::Settings.new(context.environment, VirtuosoPlugin) | ||
| 17 | + end | ||
| 18 | + | ||
| 19 | + def virtuoso_client | ||
| 20 | + @virtuoso_client ||= RDF::Virtuoso::Repository.new("#{settings.virtuoso_uri}/sparql", :update_uri => "#{settings.virtuoso_uri}/sparql-auth", :username => settings.virtuoso_username, :password => settings.virtuoso_password, :auth_method => 'digest', :timeout => 30) | ||
| 21 | + end | ||
| 22 | + | ||
| 23 | + def stylesheet? | ||
| 24 | + true | ||
| 25 | + end | ||
| 26 | + | ||
| 27 | +end |
| @@ -0,0 +1,80 @@ | @@ -0,0 +1,80 @@ | ||
| 1 | +#inspired by https://github.com/code4lib/ruby-oai/blob/master/lib/oai/harvester/harvest.rb | ||
| 2 | +class VirtuosoPlugin::DspaceHarvest | ||
| 3 | + | ||
| 4 | + DC_CONVERSION = [:title, :creator, :subject, :description, :date, :type, :identifier, :language, :rights, :format] | ||
| 5 | + | ||
| 6 | + def initialize(environment) | ||
| 7 | + @environment = environment | ||
| 8 | + end | ||
| 9 | + | ||
| 10 | + attr_reader :environment | ||
| 11 | + | ||
| 12 | + def plugin | ||
| 13 | + @plugin ||= VirtuosoPlugin.new(self) | ||
| 14 | + end | ||
| 15 | + | ||
| 16 | + delegate :settings, :to => :plugin | ||
| 17 | + | ||
| 18 | + def dspace_client | ||
| 19 | + @dspace_client ||= OAI::Client.new("#{settings.dspace_uri}/oai/request") | ||
| 20 | + end | ||
| 21 | + | ||
| 22 | + def triplify(record) | ||
| 23 | + metadata = VirtuosoPlugin::DublinCoreMetadata.new(record.metadata) | ||
| 24 | + puts "triplify #{record.header.identifier}" | ||
| 25 | + | ||
| 26 | + DC_CONVERSION.each do |c| | ||
| 27 | + values = [metadata.send(c)].flatten.compact | ||
| 28 | + values.each do |value| | ||
| 29 | + query = RDF::Virtuoso::Query.insert_data([RDF::URI.new(metadata.identifier), RDF::URI.new("http://purl.org/dc/elements/1.1/#{c}"), value]).graph(RDF::URI.new(settings.dspace_uri)) | ||
| 30 | + plugin.virtuoso_client.insert(query) | ||
| 31 | + end | ||
| 32 | + end | ||
| 33 | + end | ||
| 34 | + | ||
| 35 | + def run | ||
| 36 | + harvest_time = Time.now.utc | ||
| 37 | + params = settings.last_harvest ? {:from => settings.last_harvest.utc} : {} | ||
| 38 | + puts "starting harvest #{params} #{settings.dspace_uri} #{settings.virtuoso_uri}" | ||
| 39 | + begin | ||
| 40 | + records = dspace_client.list_records(params) | ||
| 41 | + records.each do |record| | ||
| 42 | + triplify(record) | ||
| 43 | + end | ||
| 44 | + rescue OAI::Exception => ex | ||
| 45 | + puts ex.to_s | ||
| 46 | + if ex.code != 'noRecordsMatch' | ||
| 47 | + puts "unexpected error" | ||
| 48 | + raise ex | ||
| 49 | + end | ||
| 50 | + end | ||
| 51 | + settings.last_harvest = harvest_time | ||
| 52 | + settings.save! | ||
| 53 | + puts "ending harvest #{harvest_time}" | ||
| 54 | + end | ||
| 55 | + | ||
| 56 | + def start(from_start = false) | ||
| 57 | + if find_job.empty? | ||
| 58 | + if from_start | ||
| 59 | + settings.last_harvest = nil | ||
| 60 | + settings.save! | ||
| 61 | + end | ||
| 62 | + | ||
| 63 | + job = VirtuosoPlugin::DspaceHarvest::Job.new(@environment.id) | ||
| 64 | + Delayed::Job.enqueue(job) | ||
| 65 | + end | ||
| 66 | + end | ||
| 67 | + | ||
| 68 | + def find_job | ||
| 69 | + Delayed::Job.where(:handler => "--- !ruby/struct:VirtuosoPlugin::DspaceHarvest::Job\nenvironment_id: #{@environment.id}\n") | ||
| 70 | + end | ||
| 71 | + | ||
| 72 | + class Job < Struct.new(:environment_id) | ||
| 73 | + def perform | ||
| 74 | + environment = Environment.find(environment_id) | ||
| 75 | + harvest = VirtuosoPlugin::DspaceHarvest.new(environment) | ||
| 76 | + harvest.run | ||
| 77 | + end | ||
| 78 | + end | ||
| 79 | + | ||
| 80 | +end |
plugins/virtuoso/lib/virtuoso_plugin/dublin_core_metadata.rb
0 → 100644
| @@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
| 1 | +class VirtuosoPlugin::DublinCoreMetadata | ||
| 2 | + | ||
| 3 | + include OAI::XPath | ||
| 4 | + | ||
| 5 | + attr_accessor :date, :title, :creator, :subject, :description, :date, :type, :identifier, :language, :rights, :format | ||
| 6 | + | ||
| 7 | + def initialize(element) | ||
| 8 | + @title = xpath(element, './/dc:title') | ||
| 9 | + @creator = xpath(element, './/dc:creator') | ||
| 10 | + @subject = xpath_all(element, './/dc:subject').map(&:text) | ||
| 11 | + @description = xpath(element, './/dc:description') | ||
| 12 | + @date = xpath(element, './/dc:date') | ||
| 13 | + @type = xpath(element, './/dc:type') | ||
| 14 | + @identifier = xpath(element, './/dc:identifier') | ||
| 15 | + @language = xpath(element, './/dc:language') | ||
| 16 | + @rights = xpath_all(element, './/dc:rights').map(&:text) | ||
| 17 | + @format = xpath(element, './/dc:format') | ||
| 18 | + end | ||
| 19 | + | ||
| 20 | +end |
plugins/virtuoso/lib/virtuoso_plugin/triples_template.rb
0 → 100644
| @@ -0,0 +1,38 @@ | @@ -0,0 +1,38 @@ | ||
| 1 | +class VirtuosoPlugin::TriplesTemplate < Article | ||
| 2 | + | ||
| 3 | + def self.short_description | ||
| 4 | + _('Triples template') | ||
| 5 | + end | ||
| 6 | + | ||
| 7 | + def self.description | ||
| 8 | + _('Triples template') | ||
| 9 | + end | ||
| 10 | + | ||
| 11 | + settings_items :query, :type => :string | ||
| 12 | + settings_items :template, :type => :string | ||
| 13 | + | ||
| 14 | + attr_accessible :query, :template | ||
| 15 | + | ||
| 16 | + def to_html(options = {}) | ||
| 17 | + article = self | ||
| 18 | + proc do | ||
| 19 | + render :file => 'content_viewer/triples_template', :locals => {:article => article} | ||
| 20 | + end | ||
| 21 | + end | ||
| 22 | + | ||
| 23 | + def plugin | ||
| 24 | + @plugin ||= VirtuosoPlugin.new(self) | ||
| 25 | + end | ||
| 26 | + | ||
| 27 | + def template_content | ||
| 28 | + begin | ||
| 29 | + results = plugin.virtuoso_client.query(query) | ||
| 30 | + liquid_template = Liquid::Template.parse("{% for row in results %}#{template}{% endfor %}") | ||
| 31 | + liquid_template.render('results' => results) | ||
| 32 | + rescue => ex | ||
| 33 | + logger.info ex.to_s | ||
| 34 | + "Failed to process the template" | ||
| 35 | + end | ||
| 36 | + end | ||
| 37 | + | ||
| 38 | +end |
plugins/virtuoso/test/functional/virtuoso_plugin_admin_controller_test.rb
0 → 100644
| @@ -0,0 +1,45 @@ | @@ -0,0 +1,45 @@ | ||
| 1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
| 2 | + | ||
| 3 | +class VirtuosoPluginAdminControllerTest < ActionController::TestCase | ||
| 4 | + | ||
| 5 | + def setup | ||
| 6 | + @environment = Environment.default | ||
| 7 | + @profile = create_user('profile').person | ||
| 8 | + login_as(@profile.identifier) | ||
| 9 | + end | ||
| 10 | + | ||
| 11 | + attr_reader :environment | ||
| 12 | + | ||
| 13 | + should 'save virtuoso plugin settings' do | ||
| 14 | + post :index, :settings => {'virtuoso_uri' => 'http://virtuoso.noosfero.com', | ||
| 15 | + 'virtuoso_username' => 'username', | ||
| 16 | + 'virtuoso_password' => 'password', | ||
| 17 | + 'dspace_uri' => 'http://dspace.noosfero.com'} | ||
| 18 | + @settings = Noosfero::Plugin::Settings.new(environment.reload, VirtuosoPlugin) | ||
| 19 | + assert_equal 'http://virtuoso.noosfero.com', @settings.settings[:virtuoso_uri] | ||
| 20 | + assert_equal 'username', @settings.settings[:virtuoso_username] | ||
| 21 | + assert_equal 'password', @settings.settings[:virtuoso_password] | ||
| 22 | + assert_equal 'http://dspace.noosfero.com', @settings.settings[:dspace_uri] | ||
| 23 | + assert_redirected_to :action => 'index' | ||
| 24 | + end | ||
| 25 | + | ||
| 26 | + should 'redirect to index after save' do | ||
| 27 | + post :index, :settings => {"virtuoso_uri" => 'http://virtuoso.noosfero.com'} | ||
| 28 | + assert_redirected_to :action => 'index' | ||
| 29 | + end | ||
| 30 | + | ||
| 31 | + should 'create delayed job to start harvest on force action' do | ||
| 32 | + harvest = VirtuosoPlugin::DspaceHarvest.new(environment) | ||
| 33 | + assert !harvest.find_job.present? | ||
| 34 | + get :force_harvest | ||
| 35 | + assert harvest.find_job.present? | ||
| 36 | + end | ||
| 37 | + | ||
| 38 | + should 'force harvest from start' do | ||
| 39 | + get :force_harvest, :from_start => true | ||
| 40 | + harvest = VirtuosoPlugin::DspaceHarvest.new(environment) | ||
| 41 | + assert harvest.find_job.present? | ||
| 42 | + assert_equal nil, harvest.settings.last_harvest | ||
| 43 | + end | ||
| 44 | + | ||
| 45 | +end |
| @@ -0,0 +1 @@ | @@ -0,0 +1 @@ | ||
| 1 | +require File.dirname(__FILE__) + '/../../../test/test_helper' |
| @@ -0,0 +1,25 @@ | @@ -0,0 +1,25 @@ | ||
| 1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
| 2 | + | ||
| 3 | +class DspaceHarvestTest < ActiveSupport::TestCase | ||
| 4 | + | ||
| 5 | + def setup | ||
| 6 | + @environment = Environment.default | ||
| 7 | + end | ||
| 8 | + | ||
| 9 | + attr_reader :environment | ||
| 10 | + | ||
| 11 | + should 'create delayed job when start' do | ||
| 12 | + harvest = VirtuosoPlugin::DspaceHarvest.new(environment) | ||
| 13 | + assert !harvest.find_job.present? | ||
| 14 | + harvest.start | ||
| 15 | + assert harvest.find_job.present? | ||
| 16 | + end | ||
| 17 | + | ||
| 18 | + should 'not duplicate harvest job' do | ||
| 19 | + harvest = VirtuosoPlugin::DspaceHarvest.new(environment) | ||
| 20 | + assert_difference "harvest.find_job.count", 1 do | ||
| 21 | + 5.times { harvest.start } | ||
| 22 | + end | ||
| 23 | + end | ||
| 24 | + | ||
| 25 | +end |
| @@ -0,0 +1,29 @@ | @@ -0,0 +1,29 @@ | ||
| 1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
| 2 | + | ||
| 3 | +class TriplesTemplateTest < ActiveSupport::TestCase | ||
| 4 | + | ||
| 5 | + def setup | ||
| 6 | + @article = VirtuosoPlugin::TriplesTemplate.new | ||
| 7 | + end | ||
| 8 | + | ||
| 9 | + attr_reader :article | ||
| 10 | + | ||
| 11 | + should 'evaluate template using query results' do | ||
| 12 | + article.stubs(:plugin).returns(mock) | ||
| 13 | + article.plugin.expects(:virtuoso_client).at_least_once.returns(mock) | ||
| 14 | + article.plugin.virtuoso_client.expects(:query).returns([{'var' => 'Hello '}, {'var' => 'World'}]) | ||
| 15 | + article.template = "{{row.var}}" | ||
| 16 | + | ||
| 17 | + assert_equal 'Hello World', article.template_content | ||
| 18 | + end | ||
| 19 | + | ||
| 20 | + should 'display error message when failed to execute the query' do | ||
| 21 | + article.stubs(:plugin).returns(mock) | ||
| 22 | + article.plugin.expects(:virtuoso_client).at_least_once.returns(mock) | ||
| 23 | + article.plugin.virtuoso_client.expects(:query).raises(RuntimeError.new) | ||
| 24 | + article.template = "{{row.var}}" | ||
| 25 | + | ||
| 26 | + assert_equal "Failed to process the template", article.template_content | ||
| 27 | + end | ||
| 28 | + | ||
| 29 | +end |
| @@ -0,0 +1,16 @@ | @@ -0,0 +1,16 @@ | ||
| 1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
| 2 | + | ||
| 3 | +class VirtuosoPluginTest < ActiveSupport::TestCase | ||
| 4 | + | ||
| 5 | + def setup | ||
| 6 | + @environment = Environment.default | ||
| 7 | + @plugin = VirtuosoPlugin.new | ||
| 8 | + end | ||
| 9 | + | ||
| 10 | + attr_reader :plugin | ||
| 11 | + | ||
| 12 | + should 'define a new content' do | ||
| 13 | + assert_equal [VirtuosoPlugin::TriplesTemplate], plugin.content_types | ||
| 14 | + end | ||
| 15 | + | ||
| 16 | +end |
plugins/virtuoso/views/cms/virtuoso_plugin/_triples_template.html.erb
0 → 100644
| @@ -0,0 +1,19 @@ | @@ -0,0 +1,19 @@ | ||
| 1 | +<div class="virtuoso-triples-template"> | ||
| 2 | + <%= required_fields_message %> | ||
| 3 | + | ||
| 4 | + <%= render :file => 'shared/tiny_mce' %> | ||
| 5 | + | ||
| 6 | + <%= required labelled_form_field(_('Title'), text_field(:article, 'name', :size => '64', :maxlength => 150)) %> | ||
| 7 | + <%= labelled_form_field(_('SPARQL Query'), text_area(:article, :query, :style => 'width: 98%; height: 120px;')) %> | ||
| 8 | + | ||
| 9 | + <div class="template"> | ||
| 10 | + <span class="label"><%= _('Template') %></span> | ||
| 11 | + <span class="reference" style="float: right;"><a href="https://github.com/Shopify/liquid/wiki/Liquid-for-Designers"><%= _('Template reference') %></a></span> | ||
| 12 | + <span class="input"> | ||
| 13 | + <%= text_area(:article, :template, :style => 'width: 98%; height: 200px;', :class => 'mceEditor') %> | ||
| 14 | + </span> | ||
| 15 | + </div> | ||
| 16 | + | ||
| 17 | + <%= render :partial => 'shared/lead_and_body', :locals => {:tiny_mce => true} %> | ||
| 18 | + <%= render :partial => 'general_fields' %> | ||
| 19 | +</div> |
plugins/virtuoso/views/content_viewer/triples_template.html.erb
0 → 100644
plugins/virtuoso/views/virtuoso_plugin_admin/index.html.erb
0 → 100644
| @@ -0,0 +1,35 @@ | @@ -0,0 +1,35 @@ | ||
| 1 | +<h1><%= _('Virtuoso settings')%></h1> | ||
| 2 | + | ||
| 3 | +<%= form_for(:settings) do |f| %> | ||
| 4 | + | ||
| 5 | + <strong> | ||
| 6 | + <%= labelled_form_field _('Virtuoso URL:'), f.text_field(:virtuoso_uri) %> | ||
| 7 | + <%= labelled_form_field _('Virtuoso Username:'), f.text_field(:virtuoso_username) %> | ||
| 8 | + <%= labelled_form_field _('Virtuoso Password:'), f.password_field(:virtuoso_password) %> | ||
| 9 | + <%= labelled_form_field _('DSpace URL:'), f.text_field(:dspace_uri) %> | ||
| 10 | + </strong> | ||
| 11 | + | ||
| 12 | + <% button_bar do %> | ||
| 13 | + <%= submit_button(:save, _('Save'), :cancel => {:controller => 'plugins', :action => 'index'}) %> | ||
| 14 | + <% end %> | ||
| 15 | + | ||
| 16 | +<% end %> | ||
| 17 | + | ||
| 18 | +<hr/> | ||
| 19 | +<div class="harvest"> | ||
| 20 | + <% if @settings.last_harvest %> | ||
| 21 | + <div class="date"> | ||
| 22 | + <span class="label"><strong><%= _('Last execution:') %></strong></span> | ||
| 23 | + <span class="value"><%= time_ago_as_sentence @settings.last_harvest %></span> | ||
| 24 | + </div> | ||
| 25 | +<% end %> | ||
| 26 | + <br/> | ||
| 27 | + <div class="actions"> | ||
| 28 | + <% if @harvest_running %> | ||
| 29 | + <%= _('Running...') %> | ||
| 30 | + <% else %> | ||
| 31 | + <%= button :next, _('Force harvest'), :action => :force_harvest %> | ||
| 32 | + <%= button :next, _('Force harvest from start'), :action => :force_harvest, :from_start => true %> | ||
| 33 | + <% end %> | ||
| 34 | + </div> | ||
| 35 | +</div> |