Commit db219627f45001a8a4438229acd44114a3953856

Authored by Victor Costa
2 parents b3719bed 8ae986e8

Merge branch 'virtuoso_integration' into stable

plugins/virtuoso/Gemfile 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +gem 'rdf'
  2 +gem 'rdf-virtuoso'
  3 +gem 'oai'
  4 +gem 'liquid'
... ...
plugins/virtuoso/controllers/virtuoso_plugin_admin_controller.rb 0 → 100644
... ... @@ -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
... ...
plugins/virtuoso/lib/ext/literal.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +class RDF::Literal
  2 +
  3 + def to_liquid
  4 + value
  5 + end
  6 +
  7 +end
... ...
plugins/virtuoso/lib/ext/solution.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +class RDF::Query::Solution
  2 +
  3 + def to_liquid
  4 + HashWithIndifferentAccess.new(to_hash)
  5 + end
  6 +
  7 +end
... ...
plugins/virtuoso/lib/virtuoso_plugin.rb 0 → 100644
... ... @@ -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
... ...
plugins/virtuoso/lib/virtuoso_plugin/dspace_harvest.rb 0 → 100644
... ... @@ -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 @@
  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 @@
  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/public/style.css 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +.virtuoso-triples-template .template {
  2 + margin: 10px 0;
  3 +}
... ...
plugins/virtuoso/test/functional/virtuoso_plugin_admin_controller_test.rb 0 → 100644
... ... @@ -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
... ...
plugins/virtuoso/test/test_helper.rb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +require File.dirname(__FILE__) + '/../../../test/test_helper'
... ...
plugins/virtuoso/test/unit/dspace_harvest_test.rb 0 → 100644
... ... @@ -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
... ...
plugins/virtuoso/test/unit/triples_template_test.rb 0 → 100644
... ... @@ -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
... ...
plugins/virtuoso/test/unit/virtuoso_plugin_test.rb 0 → 100644
... ... @@ -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 @@
  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
... ... @@ -0,0 +1,7 @@
  1 +<div class="body">
  2 + <%= article.body %>
  3 +</div>
  4 +
  5 +<div class="results">
  6 + <%= article.template_content %>
  7 +</div>
... ...
plugins/virtuoso/views/virtuoso_plugin_admin/index.html.erb 0 → 100644
... ... @@ -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>
... ...