SysMO-DB/seek

View on GitHub
app/controllers/models_controller.rb

Summary

Maintainability
F
4 days
Test Coverage
require 'libxml'
require 'bives'

class ModelsController < ApplicationController

  include WhiteListHelper
  include IndexPager
  include DotGenerator
  include Seek::AssetsCommon

  before_filter :models_enabled?
  before_filter :find_assets, :only => [ :index ]
  before_filter :find_and_authorize_requested_item, :except => [ :build,:index, :new, :create,:create_model_metadata,:update_model_metadata,:delete_model_metadata,:request_resource,:preview,:test_asset_url, :update_annotations_ajax]
  before_filter :find_display_asset, :only=>[:show,:download,:execute,:builder,:simulate,:submit_to_jws,:matching_data,:visualise,:export_as_xgmml,:compare_versions]

  before_filter :jws_enabled,:only=>[:builder,:simulate,:submit_to_jws]

  before_filter :find_other_version,:only=>[:compare_versions]

  include Seek::Publishing::PublishingCommon

  include Seek::BreadCrumbs

  include Bives

  @@model_builder = Seek::JWS::Builder.new

  def find_other_version
    version = params[:other_version]
    @other_version = @model.find_version(version)
    if version.nil? || @other_version.nil?
      flash[:error] = "The other version to compare with was not specified, or it does not exist"
      redirect_to model_path(@model,:version=>@display_model.version)
    end
  end

  def compare_versions
    if params[:file_id]
      @blob1 = @display_model.sbml_content_blobs.find{|b| b.id.to_s == params[:file_id]}
    else
      @blob1 = @display_model.sbml_content_blobs.first
    end
    if params[:other_file_id]
      @blob2 = @other_version.sbml_content_blobs.find{|b| b.id.to_s == params[:other_file_id]}
    else
      @blob2 = @other_version.sbml_content_blobs.first
    end

    if @blob1 && @blob2
      file1=@blob1.filepath
      file2=@blob2.filepath
      begin
        json = compare file1,file2,["reportHtml","crnJson","json","SBML"]
        @crn = JSON.parse(json)["crnJson"]
        @comparison_html = JSON.parse(json)["reportHtml"]
      rescue Exception=>e
        flash.now[:error]="there was an error trying to compare the two versions - #{e.message}"
      end
    else
      flash.now[:error]="One of the version files could not be found, or you are not authorized to examine it"
    end



  end

  def export_as_xgmml
      type =  params[:type]
      body = @_request.body.read
      orig_doc = find_xgmml_doc @display_model
      head = orig_doc.to_s.split("<graph").first
      xgmml_doc = head + body

      xgmml_file =  "model_#{@model.id}_version_#{@display_model.version}_export.xgmml"
      tmp_file= Tempfile.new("#{xgmml_file}","#{Rails.root}/tmp/")
      File.open(tmp_file.path,"w") do |tmp|
        tmp.write xgmml_doc
      end

      send_file tmp_file.path, :type=>"#{type}", :disposition=>'attachment',:filename=>xgmml_file
      tmp_file.close
  end

  def visualise
    raise Exception.new("This #{t('model')} does not support Cytoscape") unless @display_model.contains_xgmml?
     # for xgmml file
     doc = find_xgmml_doc @display_model
     # convert " to \" and newline to \n
     #e.g.  "... <att type=\"string\" name=\"canonicalName\" value=\"CHEMBL178301\"/>\n ...  "
     @graph = %Q("#{doc.root.to_s.gsub(/"/, '\"').gsub!("\n",'\n')}")
     render :cytoscape_web,:layout => false
  end

  # GET /models
  # GET /models.xml

  def new_version
    if handle_upload_data
      comments = params[:revision_comment]

      respond_to do |format|
        create_new_version  comments
        create_content_blobs
        create_model_image @model,params[:model_image]
        format.html {redirect_to @model }
      end
    else
      flash[:error]=flash.now[:error]
      redirect_to @model
    end
  end

  def delete_model_metadata
    attribute=params[:attribute]
    if attribute=="model_type"
      delete_model_type params
    elsif attribute=="model_format"
      delete_model_format params
    end
  end

  def builder
    saved_file=params[:saved_file]
    error=nil
    supported=false
    begin
      if saved_file
        supported=true
        @params_hash,@attribution_annotations,@saved_file,@objects_hash,@error_keys = @@model_builder.saved_file_builder_content saved_file
      else
        supported = @display_model.is_jws_supported?
        if supported
          content_blob = @display_model.jws_supported_content_blobs.first
          @params_hash,@attribution_annotations,@saved_file,@objects_hash,@error_keys = @@model_builder.builder_content content_blob
        end
      end
    rescue Exception=>e
      error=e
      logger.error "Error submitting to JWS Online OneStop - #{e.message}"
      raise e unless Rails.env=="production"
    end

    respond_to do |format|
      if error
        flash[:error]="JWS Online encountered a problem processing this model."
        format.html { redirect_to model_path(@model,:version=>@display_model.version)}
      elsif !supported
        flash[:error]="This #{t('model')} is of neither SBML or JWS Online (Dat) format so cannot be used with JWS Online"
        format.html { redirect_to model_path(@model,:version=>@display_model.version)}
      else
        format.html
      end
    end
  end

  def submit_to_jws
    following_action=params.delete("following_action")
    error=nil

    #FIXME: currently we have to assume that a model with multiple files only contains 1 model file that would be executed on jws online, and only the first one is chosen
    raise Exception.new("JWS Online is not supported for this model") unless @model.is_jws_supported?
    content_blob = @model.jws_supported_content_blobs.first

    begin
      if following_action == "annotate"
        @params_hash,@attribution_annotations,@species_annotations,@reaction_annotations,@search_results,@cached_annotations,@saved_file,@error_keys = Seek::JWS::Annotator.new.annotate params
      else
        @params_hash,@attribution_annotations,@saved_file,@objects_hash,@error_keys = @@model_builder.construct params
      end
    rescue Exception => e
      error=e
      raise e unless Rails.env == "production"
    end

    if (!error && @error_keys.empty?)
      if following_action == "save_new_version"
        model_format=params.delete("saved_model_format") #only used for saving as a new version
        new_version_filename=params.delete("new_version_filename")
        new_version_comments=params.delete("new_version_comments")
        if model_format == "dat"
          url=@@model_builder.saved_dat_download_url @saved_file
        elsif model_format == "sbml"
          url=@@model_builder.sbml_download_url @saved_file
        end
        if url
          downloader=Seek::RemoteDownloader.new
          data_hash = downloader.get_remote_data url
          File.open(data_hash[:data_tmp_path],"r") do |f|
            content_blob = @model.content_blobs.build(:data=>f.read)
          end
          content_blob.content_type=model_format=="sbml" ? "text/xml" : "text/plain"
          content_blob.original_filename=new_version_filename
        end
      end
    end

    respond_to do |format|
      if error
        flash[:error]="JWS Online encountered a problem processing this model."
        format.html { render :action=>"builder" }
      elsif @error_keys.empty? && following_action == "simulate"
        @modelname=@saved_file
        @no_sidebar=true
        format.html {render :simulate}
      elsif @error_keys.empty? && following_action == "annotate"
        format.html {render :action=>"annotator"}
      elsif @error_keys.empty? && following_action == "save_new_version"
        create_new_version(new_version_comments)
        content_blob.asset_version = @model.version
        content_blob.save!
        format.html {redirect_to  model_path(@model,:version=>@model.version) }
      else
        format.html { render :action=>"builder" }
      end
    end
  end

  def submit_to_sycamore
    @model = Model.find_by_id(params[:id])
    @display_model = @model.find_version(params[:version])
    error_message = nil
    if !Seek::Config.sycamore_enabled
      error_message = "Interaction with Sycamore is currently disabled"
    elsif !@model.can_download? && (params[:code].nil? || (params[:code] && !@model.auth_by_code?(params[:code])))
      error_message = "You are not allowed to simulate this #{t('model')} with Sycamore"
    end

    render :update do |page|
      if error_message.blank?
        page['sbml_model'].value = IO.read(@display_model.sbml_content_blobs.first.filepath).gsub(/\n/, '')
        page['sycamore-form'].submit()
      else
        page.alert(error_message)
      end
    end
  end

 def simulate
    error=nil
    begin
      if @display_model.is_jws_supported?
        @modelname = Seek::JWS::Simulator.new.simulate(@display_model.jws_supported_content_blobs.first)
      end
    rescue Exception=>e
      Rails.logger.error("Problem simulating #{t('model')} on JWS Online #{e}")
      raise e unless Rails.env=="production"
      error=e
    end

    respond_to do |format|
      if error
        flash[:error]="JWS Online encountered a problem processing this model."
        format.html { redirect_to(@model, :version=>@display_model.version) }
      elsif !@display_model.is_jws_supported?
        flash[:error]="This #{t('model')} is of neither SBML or JWS Online (Dat) format so cannot be used with JWS Online"
        format.html { redirect_to(@model, :version=>@display_model.version) }
      else
        @no_sidebar=true
         format.html { render :simulate }
      end
    end
  end

  def update_model_metadata
    attribute=params[:attribute]
    if attribute=="model_type"
      update_model_type params
    elsif attribute=="model_format"
      update_model_format params
    end
  end

  def delete_model_type params
    id=params[:selected_model_type_id]
    model_type=ModelType.find(id)
    success=false
    if (model_type.models.empty?)
      if model_type.delete
        msg="OK. #{model_type.title} was successfully removed."
        success=true
      else
        msg="ERROR. There was a problem removing #{model_type.title}"
      end
    else
      msg="ERROR - Cannot delete #{model_type.title} because it is in use."
    end

    render :update do |page|
      page.replace_html "model_type_selection",collection_select(:model, :model_type_id, ModelType.all, :id, :title, {:include_blank=>"Not specified"},{:onchange=>"model_type_selection_changed();" })
      page.replace_html "model_type_info","#{msg}<br/>"
      info_colour= success ? "green" : "red"
      page << "$('model_type_info').style.color='#{info_colour}';"
      page.visual_effect :appear, "model_type_info"
    end

  end

  def delete_model_format params
    id=params[:selected_model_format_id]
    model_format=ModelFormat.find(id)
    success=false
    if (model_format.models.empty?)
      if model_format.delete
        msg="OK. #{model_format.title} was successfully removed."
        success=true
      else
        msg="ERROR. There was a problem removing #{model_format.title}"
      end
    else
      msg="ERROR - Cannot delete #{model_format.title} because it is in use."
    end

    render :update do |page|
      page.replace_html "model_format_selection",collection_select(:model, :model_format_id, ModelFormat.all, :id, :title, {:include_blank=>"Not specified"},{:onchange=>"model_format_selection_changed();" })
      page.replace_html "model_format_info","#{msg}<br/>"
      info_colour= success ? "green" : "red"
      page << "$('model_format_info').style.color='#{info_colour}';"
      page.visual_effect :appear, "model_format_info"
    end
  end


  # GET /models/1
  # GET /models/1.xml
  def show
    # store timestamp of the previous last usage
    @last_used_before_now = @model.last_used_at


    @model.just_used

    respond_to do |format|
      format.html # show.html.erb
      format.xml
      format.rdf { render :template=>'rdf/show'}
    end
  end

  # GET /models/new
  # GET /models/new.xml
  def new
    @model=Model.new
    @content_blob= ContentBlob.new
    respond_to do |format|
      if User.logged_in_and_member?
        format.html # new.html.erb
      else
        flash[:error] = "You are not authorized to upload new Models. Only members of known projects, institutions or work groups are allowed to create new content."
        format.html { redirect_to models_path }
      end
    end
  end

  # GET /models/1/edit
  def edit

  end

  # POST /models
  # POST /models.xml
  def create
    if handle_upload_data
      @model = Model.new(params[:model])

      @model.policy.set_attributes_with_sharing params[:sharing], @model.projects

      update_annotations @model
      update_scales @model
      build_model_image @model,params[:model_image]
      assay_ids = params[:assay_ids] || []
      respond_to do |format|
        if @model.save

          create_content_blobs
          # update attributions
          Relationship.create_or_update_attributions(@model, params[:attributions])

          # update related publications
          Relationship.create_or_update_attributions(@model, params[:related_publication_ids].collect {|i| ["Publication", i.split(",").first]}, Relationship::RELATED_TO_PUBLICATION) unless params[:related_publication_ids].nil?

          #Add creators
          AssetsCreator.add_or_update_creator_list(@model, params[:creators])

          flash[:notice] = "#{t('model')} was successfully uploaded and saved."
          format.html { redirect_to model_path(@model) }
          Assay.find(assay_ids).each do |assay|
            if assay.can_edit?
              assay.relate(@model)
            end
          end
        else
          format.html {
            render :action => "new"
          }
        end
      end
    else
      handle_upload_data_failure
    end

  end

  # PUT /models/1
  # PUT /models/1.xml
  def update
    # remove protected columns (including a "link" to content blob - actual data cannot be updated!)
    if params[:model]
      [:contributor_id, :contributor_type, :original_filename, :content_type, :content_blob_id, :created_at, :updated_at, :last_used_at].each do |column_name|
        params[:model].delete(column_name)
      end

      # update 'last_used_at' timestamp on the Model
      params[:model][:last_used_at] = Time.now
    end

    update_annotations @model
    update_scales @model
    publication_params = params[:related_publication_ids].nil? ? [] : params[:related_publication_ids].collect { |i| ["Publication", i.split(",").first] }

    @model.attributes = params[:model]

    if params[:sharing]
      @model.policy_or_default
      @model.policy.set_attributes_with_sharing params[:sharing], @model.projects
    end

    assay_ids = params[:assay_ids] || []
    respond_to do |format|
      if @model.save

        # update attributions
        Relationship.create_or_update_attributions(@model, params[:attributions])

        # update related publications
        Relationship.create_or_update_attributions(@model, publication_params, Relationship::RELATED_TO_PUBLICATION)

        #update creators
        AssetsCreator.add_or_update_creator_list(@model, params[:creators])

        flash[:notice] = "#{t('model')} metadata was successfully updated."
        format.html { redirect_to model_path(@model) }
        # Update new assay_asset
        Assay.find(assay_ids).each do |assay|
          if assay.can_edit?
            assay.relate(@model)
          end
        end
        #Destroy AssayAssets that aren't needed
        assay_assets = @model.assay_assets
        assay_assets.each do |assay_asset|
          if assay_asset.assay.can_edit? and !assay_ids.include?(assay_asset.assay_id.to_s)
            AssayAsset.destroy(assay_asset.id)
          end
        end
      else
        format.html {
          render :action => "edit"
        }
      end
    end
  end

  # DELETE /models/1
  # DELETE /models/1.xml
  def destroy
    @model.destroy

    respond_to do |format|
      format.html { redirect_to(models_path) }
      format.xml  { head :ok }
    end
  end

  def preview

    element = params[:element]
    model = Model.find_by_id(params[:id])

    render :update do |page|
      if model.try :can_view?
        page.replace_html element,:partial=>"assets/resource_preview",:locals=>{:resource=>model}
      else
        page.replace_html element,:text=>"Nothing is selected to preview."
      end
    end
  end

  def request_resource
    resource = Model.find(params[:id])
    details = params[:details]

    Mailer.request_resource(current_user,resource,details,base_host).deliver

    render :update do |page|
      page[:requesting_resource_status].replace_html "An email has been sent on your behalf to <b>#{resource.managers.collect{|m| m.name}.join(", ")}</b> requesting the file <b>#{h(resource.title)}</b>."
    end
  end

  def matching_data
    #FIXME: should use the correct version
    @matching_data_items = @model.matching_data_files

    #filter authorization
    ids = @matching_data_items.collect &:primary_key
    data_files = DataFile.find_all_by_id(ids)
    authorised_ids = DataFile.authorize_asset_collection(data_files,"view").collect &:id
    @matching_data_items = @matching_data_items.select{|mdf| authorised_ids.include?(mdf.primary_key.to_i)}

    flash.now[:notice]="#{@matching_data_items.count} #{t('data_file').pluralize} found that may be relevant to this #{t('model')}"
    respond_to do |format|
      format.html
    end
  end



  protected

  def create_new_version comments
    if @model.save_as_new_version(comments)
      flash[:notice]="New version uploaded - now on version #{@model.version}"
    else
      flash[:error]="Unable to save new version"
    end
  end

  def translate_action action
    action="download" if action == "simulate"
    action="edit" if ["submit_to_jws","builder"].include?(action)
    action="view" if ["matching_data"].include?(action)
    super action
  end

  def jws_enabled
    unless Seek::Config.jws_enabled
      respond_to do |format|
        flash[:error] = "Interaction with JWS Online is currently disabled"
        format.html { redirect_to model_path(@model,:version=>@display_model.version) }
      end
      return false
    end
  end

   def build_model_image model_object, params_model_image
     unless params_model_image.blank? || params_model_image[:image_file].blank?
     
        # the creation of the new Avatar instance needs to have only one parameter - therefore, the rest should be set separately
        @model_image = ModelImage.new(params_model_image)
        @model_image.model_id = model_object.id
        @model_image.content_type = params_model_image[:image_file].content_type
        @model_image.original_filename = params_model_image[:image_file].original_filename
        model_object.model_image = @model_image
    end

   end

    def find_xgmml_doc model
      xgmml_content_blob = model.xgmml_content_blobs.first
      file = open(xgmml_content_blob.filepath)
      doc = LibXML::XML::Parser.string(file.read).parse
      doc
    end

  def create_model_image model_object, params_model_image
    build_model_image model_object, params_model_image
    model_object.save(:validate=>false)
    latest_version = model_object.latest_version
    latest_version.model_image_id = model_object.model_image_id
    latest_version.save
  end
end