Querying Data Services
| |
|
|
| |
Contents |
|
| |
|
|
In this section, you will add the ability for users to query available data services utilizing the CQL Query functionality of caGrid. Users will be able to add modifiers to their queries and save each query for later use. The results from each query will be displayed in a table for the users to view.
Generate the Query Model
In the terminal, use the Rails scaffold command to create the query model, controller and views:

GridClient %> rails generate scaffold query object:string endpoint:string modifier:string description:string user_id:integer
Now migrate the table into the current database using the rake command:

GridClient %> rake db:migrate
Globus Credential Helper Method
Add a method to app/controllers/application_controller.rb, a line to indicate it as a helper method and a global variable for storing available services:
"application_controller.rb"
helper_method :getGlobusCred
# Global Variable holding availabe services
$SERVICES
def getGlobusCred
return session[:globus]
end
Add Data Service Actions
Create a new Ruby file lib/DataServiceActions.rb and copy in the following code:
include_class "gov.nih.nci.cagrid.cqlresultset.CQLQueryResults" include_class "gov.nih.nci.cagrid.data.client.DataServiceClient" include_class "gov.nih.nci.cagrid.data.utilities.CQLQueryResultsIterator" module CqlQuery include_package "gov.nih.nci.cagrid.cqlquery" end class DataServiceActions def self.invokeNonSecureService (serviceEndpoint, objectName, modifier) puts "Querying: " + objectName begin client = DataServiceClient.new(serviceEndpoint) query = CqlQuery::CQLQuery.new target = CqlQuery::Object.new() target.setName(objectName) query.setTarget(target) # Set Modifiers if they exist queryModifier = CqlQuery::QueryModifier.new if modifier.at(0).eql?("count") queryModifier.setCountOnly(true) query.setQueryModifier(queryModifier) elsif modifier.at(0).eql?("distinct_attribute") queryModifier.setDistinctAttribute(modifier.at(1).to_java_string) query.setQueryModifier(queryModifier) elsif modifier.at(0).eql?("selected_attributes") queryModifier.setAttributeNames(modifier.drop(1).to_java(:string)) query.setQueryModifier(queryModifier) end #execute the query results = client.query(query) iter = CQLQueryResultsIterator.new(results, true) resultsXML = String.new while (iter.hasNext) singleResult = iter.next() resultsXML << singleResult end return resultsXML rescue puts "Error: " + $! end end def self.invokeSecureService (serviceEndpoint, cred, objectName, modifier) begin client = DataServiceClient.new(serviceEndpoint, cred) query = CqlQuery::CQLQuery.new target = CqlQuery::Object.new() target.setName(objectName) query.setTarget(target) # Set Modifiers if they exist queryModifier = CqlQuery::QueryModifier.new if modifier.at(0).eql?("count") queryModifier.setCountOnly(true) query.setQueryModifier(queryModifier) elsif modifier.at(0).eql?("distinct_attribute") queryModifier.setDistinctAttribute(modifier.at(1).to_java_string) query.setQueryModifier(queryModifier) elsif modifier.at(0).eql?("selected_attributes") queryModifier.setAttributeNames(modifier.drop(1).to_java(:string)) query.setQueryModifier(queryModifier) end #execute the query results = client.query(query) iter = CQLQueryResultsIterator.new(results, true) resultsXML = String.new while (iter.hasNext) singleResult = iter.next() resultsXML << singleResult end return resultsXML rescue puts "Error: " + $! end end end
| The Module CqlQuery definition makes it possible to instantiate all classes provided in the package "gov.nih.nci.cagrid.cqlquery" using the notation "CqlQuery::CQLQuery.new" or "CqlQuery::Object.new()" (parentheses are optional in Ruby). |
Update GridClient.rb
Open the file lib/GridClient.rb and add the following include statement and modules to the top of the file and the methods below to GridClientClass:
"GridClient.rb"
include_class "org.apache.axis.message.addressing.EndpointReferenceType" module Types include_package "org.apache.axis.types" end
def displayDomainObjectNames(epr)
begin
endpoint = EndpointReferenceType.new(Types::URI.new(epr.to_java_string))
domainModel = MetadataUtils.getDomainModel(endpoint)
umlClassCollection = domainModel.getExposedUMLClassCollection().getUMLClass()
objects = Array.new
umlClassCollection.each { |uml|
objectName = uml.getPackageName + "." + uml.getClassName
objects << objectName
}
return objects
rescue
puts "Error in displayDomainObjectNames: " + $!
end
end
def displayDomainObjectAttributes(epr, object)
begin
endpoint = EndpointReferenceType.new(Types::URI.new(epr.to_java_string))
domainModel = MetadataUtils.getDomainModel(endpoint)
umlClassCollection = domainModel.getExposedUMLClassCollection().getUMLClass()
attributesObjects = Array.new
umlClassCollection.each { |uml|
objectName = uml.getPackageName + "." + uml.getClassName
if objectName.eql?(object)
attributesObjects = uml.getUmlAttributeCollection.getUMLAttribute
end
}
attributes = Array.new
attributesObjects.each { |att|
attributes << att.getName
}
return attributes
rescue
puts "Error in displayDomainAttributes: " + $!
end
end
def queryDataService(service, objectName, cred, modifier)
begin
serviceEndpoint = EndpointReferenceType.new(Types::URI.new(service.to_java_string))
if(serviceEndpoint.getAddress.getScheme.eql?("http"))
result = DataServiceActions.invokeNonSecureService(serviceEndpoint, objectName, modifier)
else
result = DataServiceActions.invokeSecureService(serviceEndpoint, cred, objectName, modifier)
end
rescue
puts "Error: " + $!
end
return result
end
Update Query Controller
Replace the code in app/controllers/queries_controller.rb with following:
"queries_controller"
class QueriesController < ApplicationController
before_filter :logged_in
# GET /queries
# GET /queries.xml
def index
@queries = Array.new
Query.all.each do |query|
if query.user_id == current_user.id
@queries << query
end
end
@endpoints = Set.new
@queries.each do |query|
@endpoints << query.endpoint
end
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @queries }
end
end
# GET /queries/1
# GET /queries/1.xml
def show
@query = Query.find(params[:id])
@attributes = @query.modifier.split(',')
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @query }
end
end
# GET /queries/new
# GET /queries/new.xml
def new
@query = Query.new
@discActions = DiscoveryActions.new
$SERVICES ||= @discActions.getDataServices
@services = $SERVICES
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @query }
end
end
# GET /queries/1/edit
def edit
client = GridClientClass.new
@query = Query.find(params[:id])
@service = Service.new
@service.address = @query.endpoint
@objects = client.displayDomainObjectNames(@service.address)
@attributes = client.displayDomainObjectAttributes(@service.address, @query.object)
end
# POST /queries
# POST /queries.xml
def create
if params[:service_button]
puts "SERVICE BUTTON PRESSED"
client = GridClientClass.new
@query = Query.new(params[:query])
@service = Service.new
@service.address = params[:query][:endpoint]
@objects = client.displayDomainObjectNames(@service.address)
params[:endpoint] = @service.address
respond_to do |format|
format.html { render :action => "object" }
format.xml { render :xml => @query }
end
elsif params[:object_button]
puts "OBJECT BUTTON PRESSED"
client = GridClientClass.new
@query = Query.new
@service = Service.new
@service.address = params[:endpoint]
@query.object = params[:Object]
puts "Service::address = "
puts @service.address
@attributes = client.displayDomainObjectAttributes(@service.address, @query.object)
params[:obj] = params[:Object]
respond_to do |format|
format.html { render :action => "parameters" }
format.xml { render :xml => @queries }
end
elsif params[:change_service_button]
puts "CHANGE SERVICE BUTTON PRESSED"
# Start a new query
redirect_to(new_query_path)
elsif params[:change_object_button]
puts "CHANGE OBJECT BUTTON PRESSED"
# Jump to object.html.erb to change object value
client = GridClientClass.new
@query = Query.new
@service = Service.new
@service.address = params[:endpoint]
@objects = client.displayDomainObjectNames(@service.address)
respond_to do |format|
format.html { render :action => "object" }
format.xml { render :xml => @query }
end
else
puts "SAVING QUERY OBJECT"
puts params[:obj]
@query = Query.new(params[:query])
@query.object = params[:obj]
@query.endpoint = params[:endpoint]
@query.user_id = params[:user_id]
# Construct Modifiers
String mod = params[:modifier]
if mod.eql?("distinct_attribute")
mod = mod + "," + params[:DA][0]
elsif mod.eql?("selected_attributes")
params[:SA].each { |sa|
mod = mod + "," + sa
}
end
@query.modifier = mod
respond_to do |format|
if @query.save
format.html { redirect_to(@query, :notice => 'Query was successfully created.') }
format.xml { render :xml => @query, :status => :created, :location => @query }
else
format.html { render :action => "new" }
format.xml { render :xml => @query.errors, :status => :unprocessable_entity }
end
end
end
end
# PUT /queries/1
# PUT /queries/1.xml
def update
if params[:change_button]
redirect_to(query_service_path)
else
@query = Query.find(params[:id])
# Construct Modifiers
String mod = params[:modifier]
if mod.eql?("distinct_attribute")
mod = mod + "," + params[:DA][0]
elsif mod.eql?("selected_attributes")
params[:SA].each { |sa|
mod = mod + "," + sa
}
end
mod_hash = {"modifier" => mod}
respond_to do |format|
if @query.update_attributes(params[:query].merge(mod_hash))
format.html { redirect_to(@query, :notice => 'Query was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @query.errors, :status => :unprocessable_entity }
end
end
end
end
# DELETE /queries/1
# DELETE /queries/1.xml
def destroy
@query = Query.find(params[:id])
@query.destroy
respond_to do |format|
format.html { redirect_to(queries_url) }
format.xml { head :ok }
end
end
# GET /queries/1/result
def result
puts "RESULT CALLED"
@query = Query.find(params[:id])
@service = Service.new
@service.address = @query.endpoint
@modifier = @query.modifier.split(',')
client = GridClientClass.new
@result = client.queryDataService(@service.address, @query.object, getGlobusCred, @modifier)
@attributes = Array.new
@rows = self.processXML(@result, @modifier)
respond_to do |format|
if @rows.drop(1).length > 0
format.html
format.xml { render :xml => @query }
else
flash[:notice] = "No Results Found"
format.html
format.xml {render :xml => @query }
end
end
end
def processXML(xmlArray, modifier)
require "rexml/document"
rows = Array.new
if !xmlArray.nil?
xmlArray = "<EOF>" + xmlArray + "</EOF>"
end
@xml = REXML::Document.new xmlArray
# Process Object Query
if modifier.at(0).eql?("object")
client = GridClientClass.new
attributes = client.displayDomainObjectAttributes(@service.address, @query.object)
rows << attributes
@xml.elements.each("*/*") { |element|
values = Array.new
attributes.each { |att|
values << element.attributes[att]
}
rows << values
}
end
# Process Distinct Attribute Query
if modifier.at(0).eql?("distinct_attribute") || modifier.at(0).eql?("selected_attributes")
attributes = modifier.drop(1)
rows << attributes
@xml.elements.each("*/*") { |parent|
values = Array.new
parent.elements.each { |element|
attributes.each { |att|
if element.attributes["name"].eql?(att)
values << element.attributes["value"]
end
}
}
rows << values
}
end
# Process Count Query
if modifier.at(0).eql?("count")
rows << modifier
values = Array.new
@xml.elements.each("*/*") { |element|
values << element.attributes["count"]
}
rows << values
end
puts rows
return rows
end
end
Create Query View Files
Create three new query view files:
app/views/queries/object.html.erb
app/views/queries/parameters.html.erb
app/views/queries/result.html.erb
Now copy the corresponding code into each view file:
"object.html.erb"
<h2>Select Object from Service</h2> <%= form_for(@query) do |f| %> <%= hidden_field_tag :endpoint, params[:endpoint] %> <div class="field"> <%= f.label :Service %><br /> <b><%=h @service.address %></b> </div> <%= submit_tag 'Change Service', :name => 'change_service_button' %> <br></br> <div class="field"> <%= f.label :object %><br /> <%= select_tag "Object", options_for_select(@objects, @objects[0]) %> </div> <%= submit_tag 'Select Object', :name => 'object_button' %> <% end %> <%= link_to 'Back', queries_path %>
parameters.html.erb
<h2>Select Modifiers</h2> <%= form_for(@query) do |f| %> <div class="field"> <%= f.label :Service %><br /> <b><%=h @service.address %></b> </div> <%= submit_tag 'Change Service', :name => 'change_service_button' %> <br></br> <div class="field"> <%= f.label :Object %><br /> <b><%=h @query.object %></b> </div> <%= submit_tag 'Change Object', :name => 'change_object_button' %> <%= hidden_field_tag :endpoint, params[:endpoint] %> <% end %> </br> <%= render 'form' %> <%= link_to 'Back', queries_path %>
result.html.erb
<h1>Results</h1> <h2>Service</h2> <%= @service.address %> <h2>Object</h2> <%= @query.object %> <h2>Modifiers</h2> <% @modifier.each do |mod| %> <%= mod %><br> <% end %> <h2>Description</h2> <%= @query.description %> <h2>Results</h2> <div id="table.sample"> <table> <tr> <% @rows.at(0).each do |att|%> <th><%= att %></th> <% end %> </tr> <% @rows.drop(1).each do |row| %> <tr> <%row.each do |value| %> <td> <%= value %> </td> <% end %> </tr> <% end %> </table> </div> <BR> <%= link_to 'Back to Queries', queries_path %>
Update Existing Query View Files
Open the query index view and replace the code with the following:
"app/views/queries/index.html.erb"
<h1>Listing Queries by Service</h1> <% @endpoints.each do |endpoint| %> <div id="highlight"> <%= endpoint %> </div> <table> <tr> <th>Object</th> <th>Modifier</th> <th>Description</th> <th></th> <th></th> <th></th> <th></th> </tr> <% @queries.each do |query| %> <tr> <% if query.endpoint == endpoint %> <td><%= query.object %></td> <td><%= query.modifier.split(',')[0] %></td> <td><%= query.description %></td> <td><%= link_to 'Show', query %></td> <td><%= link_to 'Edit', edit_query_path(query) %></td> <td><%= link_to 'Destroy', query, :confirm => 'Are you sure?', :method => :delete %></td> <td><%= link_to 'Run Query', result_query_path(query) %></td> <% end %> </tr> <% end %> </table> <br /> <%= link_to 'New Query', new_query_path %>
"app/views/queries/new.html.erb"
<h2>Select Service URL</h2> <%= form_for(@query) do |f| %> <div class="field"> <%= f.label :Service %><br /> <%= f.collection_select :endpoint, @services, :address, :address %> </div> <%= submit_tag 'Select Service', :name => 'service_button' %> <% end %> <%= link_to 'Back', queries_path %>
"app/views/queries/edit.html.erb"
<h1>Editing query</h1> <%= form_for(@query) do |f| %> <div class="field"> <%= f.label :Service %><br /> <b><%=h @service.address %></b> </div> </br> <div class="field"> <%= f.label :Object %><br /> <b><%=h @query.object %></b> </div> <%= hidden_field_tag :endpoint, params[:endpoint] %> <% end %> </br> <%= render 'form' %> <%= link_to 'Show', @query %> | <%= link_to 'Back', queries_path %>
"app/views/queries/show.html.erb"
<p> <b>Service:</b> <%= @query.endpoint %> </p> <p> <b>Object:</b> <%= @query.object %> </p> <p> <b>Modifier:</b> <% for att in @attributes %> <%= att %><BR> <% end %> </p> <p> <b>Description:</b> <%= @query.description %> </p> <p> <b>User:</b> <%= User.find_by_id(@query.user_id).username.capitalize %> </p> <%= link_to 'Edit', edit_query_path(@query) %> | <%= link_to 'Back', queries_path %> | <%= link_to 'Run Query', result_query_path(@query) %>
"app/views/queries/_form.html.erb"
<%= form_for(@query) do |f| %> <% if @query.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@query.errors.count, "error") %> prohibited this query from being saved:</h2> <ul> <% @query.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <%= hidden_field_tag :endpoint, params[:endpoint] %> <%= hidden_field_tag :obj, @query.object %> <%= radio_button_tag :modifier, "object", true, :onclick => "['DA', 'SA'].each(Element.hide)" %> Object <%= radio_button_tag :modifier, "count", false, :onclick => "['DA', 'SA'].each(Element.hide)" %> Count <%= radio_button_tag :modifier, "distinct_attribute", false, :onchange => "Element.show('DA'); Element.hide('SA');" %> Distict Attribute <%= radio_button_tag :modifier, "selected_attributes", false, :onclick => "Element.show('SA'); Element.hide('DA');" %> Selected Attributes <div id="DA" style="display:none;"> <BR> <% for att in @attributes %> <%= radio_button_tag "DA[]", att %> <%= att %> <BR> <% end %> </div> <div id="SA" style="display:none;"> <BR> <% for att in @attributes %> <%= check_box_tag "SA[]", att %> <%= att %> <BR> <% end %> </div> <br></br> <div class="field"> <%= f.label :description %><br /> <%= f.text_field :description %> </div> <%= hidden_field_tag :user_id, current_user.id %> <div class="actions"> <%= f.submit %> </div> <% end %>
Update Routes
In config/routes.rb, add the member loop to the query resources line:
resources :queries do member do get 'result' end end
| This adds "result" to the other RESTful actions available to query. Now, query result pages will match other action URLs (ex. queries/3/results displays the results from query_id = 3) |
Add the other query routes to routes:
get "query" => "queries#index", :as => "query/index" get "queries/new" => "queries#new", :as => "queries/new" get "queries/object" => "queries#create", :as => "query/object" get "queries/params" => "queries#new", :as => "query/params"
Add caGrid Jars
Copy the following [^Query_Jars.zip] into the "lib/jars/caGrid/" directory:
caGrid-CQL-cql.1.0-1.4.jar
caGrid-CQL-cql.2.0-1.4.jar
caGrid-CQL-utils-1.4.jar
caGrid-data-common-1.4.jar
caGrid-data-stubs-1.4.jar
caGrid-data-utils-1.4.jar
Add Query Tab
Add this line to "app/views/layouts/application.html.erb" just below the link to services to add a link to the "queries/index" page:
<li><%=link_to "Queries", queries_path %></li>
Saving And Running Queries
Once logged in, you can navigate to http://localhost:3000/queries
or click the Query tab to start adding queries. Select "New Query" to begin constructing a new query:

From the drop down menu, select the service you wish to query and press "Select Service":

After pressing the "Select Service" button, a drop down menu of available objects will be displayed:

Once an object is selected, radio buttons will appear along with a text box to add a description. The radio buttons correspond to modifiers available through CQL Queries of caGrid Services. If either "Distinct" or "Selected" attributes is chosen, then new options should appear giving the user the ability to select which attribute values to return.

| Pressing either "Change Service" or "Change Object" will allow the user to go back and change those values before saving. However, changing these values will change later options of the form and therefore object and modifier information is not retained. |
Press the "Create Query" button to save the query with the selected modifiers and description. A message with flash saying the query has been successfully created and you will be redirected to a summary page for that query.

Clicking the "Run Query" link will execute the query and display the results in a table under a summary of the query.

Clicking the "Back To Queries" link will bring you back the query index page. This page will list all queries sorted by Service URL that have been saved by the user. Queries can be edited by clicking the "Edit" link or deleted by clicking the "Destroy" link.





