app/controllers/citero_engine/citero_engine_controller.rb
require "citero_engine/engine"
require 'digest/sha1'
require 'open-uri'
module CiteroEngine
# Logic behind the webservice. First it gathers all the resource keys and creates Citation objects out of them and then
# it gathers any and all from formats and data variables that were sent via post and creates an array out of them. If the
# array is still empty it uses the URL as an OpenURL. It then loops through the array and translates and caches (or fetches)
# each one using acts_as_citable. It then either downloads the data or redirects to another webservice.
class CiteroEngineController < ActionController::Base
# There must be a destination format, or else this whole thing doesnt make sense
before_filter :valid_to_format?
layout "citero_engine/application"
# Sends bad request if there is no destination format
def valid_to_format?
head :bad_request unless to_format
end
# Checks to see if destination format is valid and stores it in a class variable
def to_format
@to_format ||= whitelist_formats :to, params[:to_format]
end
private :to_format
# Constructs an array containing all the citations
def citations
unless defined? @citations
@citations = record_citation + resource_citation + format_citation
if @citations.empty?
@citations << open_url_citation
end
end
@citations.compact
end
def record_citation
(params[:id].nil?) ? [] :
params[:id].collect do |id|
record = CiteroEngine.acts_as_citable_class.find_by_id id if CiteroEngine.acts_as_citable_class.respond_to? :find_by_id
(record.nil?) ? (raise(ArgumentError, "This ID cannot be found.")) : record
end
end
# Constructs new citation objects with only the citation key set, returns an array
def resource_citation
resources = []
return resources if params[:resource_key].nil?
resources << Rails.cache.fetch(params[:resource_key])
resources.flatten!
resources.collect do |key|
citation = CiteroEngine.acts_as_citable_class.new()
citation.resource_key = key
citation
end
end
# Constructs new citation objects with data and source format set (the citation key is constructed automatically), returns an array
def format_citation
(params[:from_format].nil? || params[:data].nil?) ? [] :
params[:from_format].collect.with_index do |format, index|
CiteroEngine.acts_as_citable_class.new CiteroEngine.acts_as_citable_class.data_field.to_sym => params[:data].to_a[index], CiteroEngine.acts_as_citable_class.format_field.to_sym => (whitelist_formats :from, format)
end
end
# Returns a single citation object with data and format set as the url and openurl respectively
def open_url_citation
CiteroEngine.acts_as_citable_class.new CiteroEngine.acts_as_citable_class.data_field.to_sym => CGI::unescape(request.protocol+request.host_with_port+request.fullpath), CiteroEngine.acts_as_citable_class.format_field.to_sym => (whitelist_formats :from, 'openurl')
end
# Maps the output and caches it, alternatively it fetches the already cached result. Seperates each output with two new lines.
# Raises an argument error if any error is caught in mapping (usually the formats are messed up)
def map
@output ||= citations.collect { |citation| Rails.cache.fetch(citation.resource_key+to_format) { citation.send(to_format) } }.join_and_enclose *delimiters
rescue Exception => exc
raise ArgumentError, "#{exc}\n Data or source format not provided and/or mismatched. [citations => #{citations}, to_format => #{@to_format}] "
end
# Maps then decides wether its a push request or a download, catches all bad argument errors
def index
map
serve
rescue ArgumentError => exc
handle_invalid_arguments exc
end
# Pushes to a web service if that is what was requested else it downloads
def serve
@push_to ? push : download
end
# Cleans the user input and finds the associated method for that format
def whitelist_formats direction, format
# if the params are nil then it returns nil
if direction.nil? || format.nil?
return
end
# if the to format is found, it returns the method name for that to format
format_sym = format.downcase.to_sym
if (direction == :to && (Citero.to_formats.include?(format_sym) || Citero.citation_styles.include?(format_sym)))
return "to_#{format_sym}"
# if the from format is found, it returns just that because the object already knows what method to call
elsif (direction == :from && Citero.from_formats.include?(format_sym))
return format.downcase
end
# if the format is still not found, it might be a push request, check if that is the case
if CiteroEngine.push_formats.include? format_sym
@push_to = CiteroEngine.push_formats[format_sym]
@to_format = @push_to.to_format.downcase
return "#{direction.to_s}_#{@to_format}"
end
end
# For debugging purposes prints out the error. Also sends bad request header
def handle_invalid_arguments exc
logger.debug exc
head :bad_request
end
# Redirects or calls a predefined method depending on the webservice selected
def push
# for redirects
if @push_to.action.eql? :redirect
# Openurl is data
@data = "#{request.protocol}#{request.host_with_port}#{request.fullpath}"
# and redirect to the url supplied by the webservice and the callback url
redirect_to @push_to.url+callback, :status => 303
elsif @push_to.action.eql? :render
# call the method this service needs
render_push
end
end
# sends the data with utf-8 encoding
def download
send_data @output.force_encoding('UTF-8'), :filename => filename, :type => @to_format.formatize.to_sym
end
# The callback url is defined here
def callback
# Starts with current url minus the querystring..
callback = "#{export_citations_url.gsub(/https?/, @push_to.callback_protocol.to_s)}?"
resource_keys = []
citations.each do |citation|
if !citation.respond_to? :new_record || citation.new_record?
resource_keys << citation.resource_key
end
end
unless resource_keys.empty?
resource_key = Digest::SHA1.hexdigest(resource_keys.sort.join)
Rails.cache.write(resource_key,resource_keys)
callback += "resource_key=#{resource_key}&"
end
citations.each do |citation|
# then adds a resource key for each cached resource
callback += (!citation.respond_to? :new_record || citation.new_record?) ? "" : "id[]=#{citation.id}&"
end
# and finally the to format
callback += "to_format=#{@to_format.formatize}"
# url encode and return
ERB::Util.url_encode(callback)
end
# Creates the filename and extension. Few are application specific
def filename
name = "export"
case @to_format
when "to_bibtex"
name += ".bib"
when "to_easybib"
name += ".json"
else
name += "." + @to_format.formatize
end
name
end
def render_push
render :layout => false, :template => @push_to.template
end
def delimiters
case @to_format
when "to_easybib"
return [",\n","[","]"]
else
return ["\n\n"]
end
end
end
end