From: Michal Fojtik <mfoj...@redhat.com> * $filter will limit entities to those who patch the filter condition. (/machines?$filter=name=machine1)
* $first and $last will slice the array of elements in collection Signed-off-by: Michal fojtik <mfoj...@redhat.com> --- server/lib/cimi/helpers.rb | 3 + server/lib/sinatra/rack_cimi_filter.rb | 107 ++++++++++++++++++++ server/tests/cimi/collections/cimi_filter_test.rb | 50 +++++++++ .../tests/cimi/collections/fixtures/sample1.json | 25 +++++ server/tests/cimi/collections/fixtures/sample1.xml | 22 ++++ 5 files changed, 207 insertions(+) create mode 100644 server/lib/sinatra/rack_cimi_filter.rb create mode 100644 server/tests/cimi/collections/cimi_filter_test.rb create mode 100644 server/tests/cimi/collections/fixtures/sample1.json create mode 100644 server/tests/cimi/collections/fixtures/sample1.xml diff --git a/server/lib/cimi/helpers.rb b/server/lib/cimi/helpers.rb index 2dba3be..98b7652 100644 --- a/server/lib/cimi/helpers.rb +++ b/server/lib/cimi/helpers.rb @@ -38,6 +38,7 @@ require_relative '../deltacloud/helpers/url_helper' require_relative '../deltacloud/helpers/deltacloud_helper' require_relative '../deltacloud/helpers/rabbit_helper' require_relative './helpers/cimi_helper' +require_relative '../sinatra/rack_cimi_filter' require_relative './models' module CIMI::Collections @@ -55,6 +56,8 @@ module CIMI::Collections helpers Deltacloud::Helpers::Application helpers CIMIHelper + use Rack::CIMIFilter + register Rack::RespondTo enable :xhtml diff --git a/server/lib/sinatra/rack_cimi_filter.rb b/server/lib/sinatra/rack_cimi_filter.rb new file mode 100644 index 0000000..69e4849 --- /dev/null +++ b/server/lib/sinatra/rack_cimi_filter.rb @@ -0,0 +1,107 @@ +module Rack + + class CIMIFilter + + require 'nokogiri' + require 'json' + + def initialize(app, no_cache_control = nil, cache_control = nil) + @app = app + end + + def call(env) + @request = Rack::Request.new(env) + status, headers, body = @app.call(env) + if @request.params.key? '$filter' + body = CIMIFilter.filter(@request.media_type, body, @request.params['$filter']) + end + if @request.params.key? '$first' and @request.params.key? '$last' + body = CIMIFilter.slice(@request.media_type, body, @request.params['$first'], @request.params['$last']) + end + [status, headers, body] + end + + def self.filter(format, body, filter) + if format == 'application/json' + JSONFilter.filter(body, filter) + elsif ['application/xml', 'text/xml'].include? format + XMLFilter.filter(body, filter) + end + end + + def self.slice(format, body, start_pos, end_pos) + if format == 'application/json' + JSONFilter.slice(body, start_pos, end_pos) + elsif ['application/xml', 'text/xml'].include? format + XMLFilter.slice(body, start_pos, end_pos) + end + end + + class JSONFilter + def self.filter(body, filter) + k, v = filter.split('=') + json = JSON::parse(body) + collection_name = json.reject { |key, val| !val.kind_of? Array } + return body if collection_name.empty? + collection_name = collection_name.keys.first + result = json[collection_name].select do |entity| + entity[k] == v + end + json[collection_name] = result + json['count'] = json[collection_name].size.to_s + JSON::dump(json) + end + + def self.slice(body, start_pos, end_pos) + json = JSON::parse(body) + collection_name = json.reject { |key, val| !val.kind_of? Array } + return body if collection_name.empty? + collection_name = collection_name.keys.first + json[collection_name] = json[collection_name][start_pos-1..end_pos] + json['count'] = json[collection_name].size.to_s + JSON::dump(json) + end + end + + class XMLFilter + def self.filter(body, filter) + k, v = filter.split('=') + xml = Nokogiri::XML(body) + return body if xml.root.name != 'Collection' + counter = 0 + xml.root.element_children.each do |node| + next if node.element_children.empty? + if (!node.at(k).nil?) && (node.at(k).text.strip == v) + counter += 1 + else + node.remove + end + end + xml.root.at('count').content = counter.to_s if xml.root.at('count') + xml.to_s + end + + def self.slice(body, start_pos, end_pos) + xml = Nokogiri::XML(body) + return body if xml.root.name != 'Collection' + counter = 0 + children = xml.root.element_children.select do |node| + !node.element_children.empty? + end + children = children[start_pos-1..end_pos] + xml.root.element_children.each do |node| + next if node.element_children.empty? + if children.include? node + counter += 1 + else + node.remove + end + end + xml.root.at('count').content = counter.to_s if xml.root.at('count') + xml.to_s + end + end + + end + +end diff --git a/server/tests/cimi/collections/cimi_filter_test.rb b/server/tests/cimi/collections/cimi_filter_test.rb new file mode 100644 index 0000000..4d52dd2 --- /dev/null +++ b/server/tests/cimi/collections/cimi_filter_test.rb @@ -0,0 +1,50 @@ +require 'rubygems' +require 'require_relative' if RUBY_VERSION < '1.9' +require 'minitest/autorun' +require_relative './common.rb' + +describe Rack::CIMIFilter do + + def sample(name) + File.read(File.join(File.dirname(__FILE__), 'fixtures', name)) + end + + it 'should filter the collection using element name and value in XML' do + result = Rack::CIMIFilter.filter('application/xml', sample('sample1.xml'), 'name=disk1') + result = Nokogiri::XML(result) + (result/'Collection/Disk').size.must_equal 1 + result.at('Collection/count').text.must_equal '1' + result.at('Collection/Disk/name').text.must_equal 'disk1' + end + + it 'should filter the collection using element name and value in JSON' do + result = Rack::CIMIFilter.filter('application/json', sample('sample1.json'), 'name=disk1') + result = JSON::parse(result) + result['disks'].size.must_equal 1 + result['count'].must_equal '1' + result['disks'][0]['name'].must_equal 'disk1' + end + + it 'should slice the collection using $start and $end params in XML' do + result = Rack::CIMIFilter.slice('application/xml', sample('sample1.xml'), 2, 3) + result = Nokogiri::XML(result) + (result/'Collection/Disk').size.must_equal 2 + result.at('Collection/count').text.must_equal '2' + result = Rack::CIMIFilter.slice('application/xml', sample('sample1.xml'), 3, 3) + result = Nokogiri::XML(result) + (result/'Collection/Disk').size.must_equal 1 + result.at('Collection/count').text.must_equal '1' + end + + it 'should slice the collection using $start and $end params in JSON' do + result = Rack::CIMIFilter.slice('application/json', sample('sample1.json'), 2, 3) + result = JSON::parse(result) + result['disks'].size.must_equal 2 + result['count'].must_equal '2' + result = Rack::CIMIFilter.slice('application/json', sample('sample1.json'), 3, 3) + result = JSON::parse(result) + result['disks'].size.must_equal 1 + result['count'].must_equal '1' + end + +end diff --git a/server/tests/cimi/collections/fixtures/sample1.json b/server/tests/cimi/collections/fixtures/sample1.json new file mode 100644 index 0000000..761b971 --- /dev/null +++ b/server/tests/cimi/collections/fixtures/sample1.json @@ -0,0 +1,25 @@ +{ + "resourceURI": "http://schemas.dmtf.org/cimi/1/DiskCollection", + "id": "testcollection", + "count": "3", + "disks": [ + { + "resourceURI": "http://schemas.dmtf.org/cimi/1/Disk", + "id": "1", + "name": "disk1", + "description": "disk1desc" + }, + { + "resourceURI": "http://schemas.dmtf.org/cimi/2/Disk", + "id": "2", + "name": "disk2", + "description": "disk3desc" + }, + { + "resourceURI": "http://schemas.dmtf.org/cimi/3/Disk", + "id": "3", + "name": "disk3", + "description": "disk3desc" + } + ] +} diff --git a/server/tests/cimi/collections/fixtures/sample1.xml b/server/tests/cimi/collections/fixtures/sample1.xml new file mode 100644 index 0000000..273215d --- /dev/null +++ b/server/tests/cimi/collections/fixtures/sample1.xml @@ -0,0 +1,22 @@ +<Collection resourceURI="http://schemas.dmtf.org/cimi/1/DiskCollection" xmlns="http://schemas.dmtf.org/cimi/1"> + <id>http://localhost/cimi/Disk</id> + <count>3</count> + <Disk> + <id>/disks/1</id> + <name>disk1</name> + <description>disk1_desc</description> + <capacity>100</capacity> + </Disk> + <Disk> + <id>/disks/2</id> + <name>disk2</name> + <description>disk2_desc</description> + <capacity>200</capacity> + </Disk> + <Disk> + <id>/disks/3</id> + <name>disk3</name> + <description>disk3_desc</description> + <capacity>300</capacity> + </Disk> +</Collection> -- 1.7.10.2