odata_server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/Gemfile +12 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README +14 -0
  4. data/Rakefile +53 -0
  5. data/VERSION +1 -0
  6. data/app/controllers/o_data_controller.rb +145 -0
  7. data/app/helpers/o_data_helper.rb +203 -0
  8. data/app/views/o_data/metadata.xml.builder +62 -0
  9. data/app/views/o_data/resource.atom.builder +8 -0
  10. data/app/views/o_data/resource.json.erb +1 -0
  11. data/app/views/o_data/service.xml.builder +11 -0
  12. data/config/routes.rb +11 -0
  13. data/init.rb +3 -0
  14. data/install.rb +1 -0
  15. data/lib/o_data/abstract_query.rb +27 -0
  16. data/lib/o_data/abstract_query/base.rb +133 -0
  17. data/lib/o_data/abstract_query/countable.rb +22 -0
  18. data/lib/o_data/abstract_query/errors.rb +117 -0
  19. data/lib/o_data/abstract_query/option.rb +58 -0
  20. data/lib/o_data/abstract_query/options/enumerated_option.rb +39 -0
  21. data/lib/o_data/abstract_query/options/expand_option.rb +81 -0
  22. data/lib/o_data/abstract_query/options/format_option.rb +19 -0
  23. data/lib/o_data/abstract_query/options/inlinecount_option.rb +20 -0
  24. data/lib/o_data/abstract_query/options/orderby_option.rb +79 -0
  25. data/lib/o_data/abstract_query/options/select_option.rb +74 -0
  26. data/lib/o_data/abstract_query/options/skip_option.rb +32 -0
  27. data/lib/o_data/abstract_query/options/top_option.rb +32 -0
  28. data/lib/o_data/abstract_query/parser.rb +77 -0
  29. data/lib/o_data/abstract_query/segment.rb +43 -0
  30. data/lib/o_data/abstract_query/segments/collection_segment.rb +24 -0
  31. data/lib/o_data/abstract_query/segments/count_segment.rb +38 -0
  32. data/lib/o_data/abstract_query/segments/entity_type_and_key_values_segment.rb +116 -0
  33. data/lib/o_data/abstract_query/segments/entity_type_segment.rb +31 -0
  34. data/lib/o_data/abstract_query/segments/links_segment.rb +49 -0
  35. data/lib/o_data/abstract_query/segments/navigation_property_segment.rb +82 -0
  36. data/lib/o_data/abstract_query/segments/property_segment.rb +50 -0
  37. data/lib/o_data/abstract_query/segments/value_segment.rb +40 -0
  38. data/lib/o_data/abstract_schema.rb +9 -0
  39. data/lib/o_data/abstract_schema/association.rb +29 -0
  40. data/lib/o_data/abstract_schema/base.rb +48 -0
  41. data/lib/o_data/abstract_schema/comparable.rb +42 -0
  42. data/lib/o_data/abstract_schema/end.rb +50 -0
  43. data/lib/o_data/abstract_schema/entity_type.rb +64 -0
  44. data/lib/o_data/abstract_schema/navigation_property.rb +37 -0
  45. data/lib/o_data/abstract_schema/property.rb +35 -0
  46. data/lib/o_data/abstract_schema/schema_object.rb +37 -0
  47. data/lib/o_data/abstract_schema/serializable.rb +79 -0
  48. data/lib/o_data/active_record_schema.rb +8 -0
  49. data/lib/o_data/active_record_schema/association.rb +130 -0
  50. data/lib/o_data/active_record_schema/base.rb +37 -0
  51. data/lib/o_data/active_record_schema/entity_type.rb +128 -0
  52. data/lib/o_data/active_record_schema/navigation_property.rb +45 -0
  53. data/lib/o_data/active_record_schema/property.rb +45 -0
  54. data/lib/o_data/active_record_schema/serializable.rb +36 -0
  55. data/lib/odata_server.rb +12 -0
  56. data/public/clientaccesspolicy.xml +13 -0
  57. data/tasks/o_data_server_tasks.rake +4 -0
  58. data/test/helper.rb +17 -0
  59. data/uninstall.rb +1 -0
  60. metadata +171 -0
@@ -0,0 +1,62 @@
1
+ xml.instruct!
2
+ xml.edmx(:Edmx, :Version => "1.0", "xmlns:edmx" => "http://schemas.microsoft.com/ado/2007/06/edmx", "xml:base" => o_data_metadata_url, "xml:id" => "") do
3
+ xml.edmx(:DataServices, "m:DataServiceVersion" => "2.0", "xmlns:m" => "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata") do
4
+ xml.tag!(:Schema, :Namespace => ODataController.schema.namespace, "xmlns:d" => "http://schemas.microsoft.com/ado/2007/08/dataservices", "xmlns" => "http://schemas.microsoft.com/ado/2007/05/edm", "xml:id" => "Schema") do
5
+ ODataController.schema.entity_types.sort_by(&:qualified_name).each do |entity_type|
6
+ xml.tag!(:EntityType, :Name => entity_type.name) do
7
+ unless entity_type.key_property.blank?
8
+ xml.tag!(:Key) do
9
+ xml.tag!(:PropertyRef, :Name => entity_type.key_property.name)
10
+ end
11
+ end
12
+
13
+ entity_type.properties.each do |property|
14
+ property_attrs = { :Name => property.name, :Type => property.return_type, :Nullable => property.nullable? }
15
+
16
+ property_attrs.merge!("m:FC_TargetPath" => "SyndicationTitle", "m:FC_ContentKind" => "text", "m:FC_KeepInContent" => "true") if property == entity_type.atom_title_property
17
+ property_attrs.merge!("m:FC_TargetPath" => "SyndicationSummary", "m:FC_ContentKind" => "text", "m:FC_KeepInContent" => "false") if property == entity_type.atom_summary_property
18
+ property_attrs.merge!("m:FC_TargetPath" => "SyndicationUpdated", "m:FC_ContentKind" => "text", "m:FC_KeepInContent" => "true") if property == entity_type.atom_updated_at_property
19
+
20
+ xml.tag!(:Property, property_attrs)
21
+ end
22
+
23
+ entity_type.navigation_properties.sort_by(&:qualified_name).each do |navigation_property|
24
+ xml.tag!(:NavigationProperty, :Name => navigation_property.name, :Relationship => navigation_property.association.qualified_name, :FromRole => navigation_property.from_end.name, :ToRole => navigation_property.to_end.name)
25
+ end
26
+ end
27
+ end
28
+
29
+ ODataController.schema.associations.sort_by(&:qualified_name).each do |association|
30
+ xml.tag!(:Association, :Name => association.name) do
31
+ xml.tag!(:End, :Role => association.from_end.name, :Type => association.from_end.return_type, :Multiplicity => association.from_end.to_multiplicity)
32
+ xml.tag!(:End, :Role => association.to_end.name, :Type => association.to_end.return_type, :Multiplicity => association.to_end.to_multiplicity)
33
+ xml.tag!(:ReferentialConstraint) do
34
+ xml.tag!(:Dependent, :Role => association.from_end.name) do
35
+ OData::ActiveRecordSchema::Association.column_names_for_from_end(association.reflection).each do |column_name|
36
+ xml.tag!(:PropertyRef, :Name => column_name)
37
+ end
38
+ end
39
+ xml.tag!(:Principal, :Role => association.to_end.name) do
40
+ OData::ActiveRecordSchema::Association.column_names_for_to_end(association.reflection).each do |column_name|
41
+ xml.tag!(:PropertyRef, :Name => column_name)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ xml.tag!(:EntityContainer, :Name => ODataController.schema.namespace, "m:IsDefaultEntityContainer" => true) do
49
+ ODataController.schema.entity_types.sort_by(&:qualified_name).each do |entity_type|
50
+ xml.tag!(:EntitySet, :Name => entity_type.plural_name, :EntityType => entity_type.qualified_name)
51
+ end
52
+
53
+ ODataController.schema.associations.sort_by(&:qualified_name).each do |association|
54
+ xml.tag!(:AssociationSet, :Name => association.name, :Association => association.qualified_name) do
55
+ xml.tag!(:End, :EntitySet => association.from_end.entity_type.plural_name, :Role => association.from_end.name)
56
+ xml.tag!(:End, :EntitySet => association.reflection.options[:polymorphic] ? association.to_end.return_type : association.to_end.entity_type.plural_name, :Role => association.to_end.name)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,8 @@
1
+ xml.instruct!
2
+
3
+ if @countable
4
+ o_data_atom_feed(xml, @query, @results, :expand => @expand_navigation_property_paths)
5
+ else
6
+ first_result = [@results].flatten.compact.first
7
+ o_data_atom_entry(xml, @query, first_result, :expand => @expand_navigation_property_paths)
8
+ end
@@ -0,0 +1 @@
1
+ <%= if @countable then o_data_json_feed(@query, @results, :expand => @expand_navigation_property_paths).to_json else o_data_json_entry(@query, [@results].flatten.compact.first, :d => true, :expand => @expand_navigation_property_paths).to_json end %>
@@ -0,0 +1,11 @@
1
+ xml.instruct!
2
+ xml.tag!(:service, "xmlns:atom" => "http://www.w3.org/2005/Atom", "xmlns" => "http://www.w3.org/2007/app", "xml:base" => o_data_service_url) do
3
+ xml.tag!(:workspace) do
4
+ xml.atom(:title, ODataController.schema.namespace)
5
+ ODataController.schema.entity_types.collect(&:plural_name).sort.each do |plural_name|
6
+ xml.tag!(:collection, :href => plural_name) do
7
+ xml.atom(:title, plural_name)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.with_options(:controller => "o_data") do |o_data|
3
+ o_data.o_data_service "/OData/OData.svc", :action => "service"
4
+ o_data.o_data_metadata "/OData/OData.svc/$metadata", :action => "metadata"
5
+ o_data.o_data_resource "/OData/OData.svc/*#{ODataController.path_param.to_s}", :action => "resource"
6
+
7
+ o_data.connect "/OData", :action => "redirect_to_service"
8
+ o_data.connect "/OData/$metadata", :action => "redirect_to_metadata"
9
+ o_data.connect "/OData/*#{ODataController.path_param.to_s}", :action => "redirect_to_resource"
10
+ end
11
+ end
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ # Include hook code here
2
+
3
+ require "o_data"
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,27 @@
1
+ require "o_data/abstract_query/errors"
2
+ require "o_data/abstract_query/countable"
3
+ require "o_data/abstract_query/base"
4
+ require "o_data/abstract_query/segment"
5
+ require "o_data/abstract_query/option"
6
+
7
+ require "o_data/abstract_query/segments/entity_type_segment"
8
+ require "o_data/abstract_query/segments/entity_type_and_key_values_segment"
9
+
10
+ require "o_data/abstract_query/segments/collection_segment"
11
+ require "o_data/abstract_query/segments/navigation_property_segment"
12
+ require "o_data/abstract_query/segments/property_segment"
13
+ require "o_data/abstract_query/segments/value_segment"
14
+ require "o_data/abstract_query/segments/links_segment"
15
+ require "o_data/abstract_query/segments/count_segment"
16
+
17
+ require "o_data/abstract_query/options/enumerated_option"
18
+
19
+ require "o_data/abstract_query/options/format_option"
20
+ require "o_data/abstract_query/options/inlinecount_option"
21
+ require "o_data/abstract_query/options/top_option"
22
+ require "o_data/abstract_query/options/skip_option"
23
+ require "o_data/abstract_query/options/orderby_option"
24
+ require "o_data/abstract_query/options/select_option"
25
+ require "o_data/abstract_query/options/expand_option"
26
+
27
+ require "o_data/abstract_query/parser"
@@ -0,0 +1,133 @@
1
+ module OData
2
+ module AbstractQuery
3
+ class Base
4
+ attr_reader :schema
5
+ attr_reader :segments, :options
6
+
7
+ def initialize(schema, segments = [], options = [])
8
+ @schema = schema
9
+
10
+ @segments = segments
11
+ @options = options
12
+ end
13
+
14
+ def inspect
15
+ "#<< #{@schema.namespace.to_s}(#{self.to_uri.inspect}) >>"
16
+ end
17
+
18
+ def Segment(*args)
19
+ segment_class = begin
20
+ if args.first.is_a?(Symbol) || args.first.is_a?(String)
21
+ "OData::AbstractQuery::Segments::#{args.shift.to_s}Segment".constantize
22
+ else
23
+ args.shift
24
+ end
25
+ end
26
+
27
+ segment = segment_class.new(self, *args)
28
+
29
+ @segments << segment
30
+ segment
31
+ end
32
+
33
+ def Option(*args)
34
+ option_class = begin
35
+ if args.first.is_a?(Symbol) || args.first.is_a?(String)
36
+ "OData::AbstractQuery::Options::#{args.shift.to_s}Option".constantize
37
+ else
38
+ args.shift
39
+ end
40
+ end
41
+
42
+ option = option_class.new(self, *args)
43
+
44
+ @options << option
45
+ option
46
+ end
47
+
48
+ def resource_path
49
+ @segments.collect(&:value).join('/')
50
+ end
51
+
52
+ def query_string
53
+ @options.collect { |o| "#{o.key.to_s}=#{o.value.to_s}" }.join('&')
54
+ end
55
+
56
+ def to_uri
57
+ [resource_path, query_string].reject(&:blank?).join('?')
58
+ end
59
+
60
+ def execute!
61
+ _execute!
62
+ end
63
+
64
+ # def entity_type
65
+ # return nil if @segments.empty?
66
+ # return nil unless @segments.last.respond_to?(:entity_type)
67
+ # @segments.last.entity_type
68
+ # end
69
+
70
+ protected
71
+
72
+ def _execute!
73
+ _segments = [@segments].flatten.compact
74
+ results = __execute!([], nil, _segments.shift, _segments)
75
+
76
+ results = with_skip_and_top_options(with_orderby_option(results))
77
+
78
+ results
79
+ end
80
+
81
+ def __execute!(seen, acc, head, rest)
82
+ return acc if head.blank?
83
+ raise Errors::InvalidSegmentContext.new(self, head) unless seen.empty? || head.can_follow?(seen.last)
84
+
85
+ results = head.execute!(acc)
86
+ raise Errors::ExecutionOfSegmentFailedValidation.new(self, head) unless head.valid?(results)
87
+
88
+ seen << head
89
+ __execute!(seen, results, rest.shift, rest)
90
+ end
91
+
92
+ private
93
+
94
+ def with_orderby_option(results)
95
+ orderby_option = @options.find { |o| o.option_name == Options::OrderbyOption.option_name }
96
+
97
+ orderby = orderby_option.blank? ? nil : orderby_option.pairs
98
+
99
+ if orderby && (entity_type = orderby_option.entity_type)
100
+ results = entity_type.sort(results, orderby)
101
+ else
102
+ results
103
+ end
104
+ end
105
+
106
+ def with_skip_and_top_options(results)
107
+ skip_option = @options.find { |o| o.option_name == Options::SkipOption.option_name }
108
+ top_option = @options.find { |o| o.option_name == Options::TopOption.option_name }
109
+
110
+ skip = skip_option.blank? ? nil : skip_option.value.to_i
111
+ top = top_option.blank? ? nil : top_option.value.to_i
112
+
113
+ if skip && top
114
+ results = results.slice(skip, top)
115
+ elsif skip
116
+ results = results.slice(skip..-1)
117
+ elsif top
118
+ results = results.slice(0, top)
119
+ else
120
+ results
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ module AbstractSchema
127
+ class Base
128
+ def Query(*args)
129
+ OData::AbstractQuery::Base.new(self, *args)
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,22 @@
1
+ module OData
2
+ module AbstractQuery
3
+ module Countable
4
+ def self.included(base)
5
+ base.send(:extend, SingletonMethods)
6
+ base.send(:include, InstanceMethods)
7
+ end
8
+
9
+ module SingletonMethods
10
+ def countable?
11
+ false
12
+ end
13
+ end
14
+
15
+ module InstanceMethods
16
+ def countable?
17
+ self.class.countable?
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,117 @@
1
+ module OData
2
+ module AbstractQuery
3
+ module Errors
4
+ class AbstractQueryException < OData::ODataException
5
+ attr_reader :query
6
+
7
+ def initialize(query)
8
+ @query = query
9
+ end
10
+
11
+ def to_s
12
+ "An unknown error has occured."
13
+ end
14
+ end
15
+
16
+ class ParseQuerySegmentException < AbstractQueryException
17
+ attr_reader :str
18
+
19
+ def initialize(query, str)
20
+ super(query)
21
+ @str = str
22
+ end
23
+
24
+ def to_s
25
+ "Resource not found for the segment '#{@str.to_s}'."
26
+ end
27
+ end
28
+
29
+ class EntityTypeNotFound < ParseQuerySegmentException
30
+ def to_s
31
+ "EntityType not found for the segment '#{self.str.to_s}'"
32
+ end
33
+ end
34
+
35
+ class PropertyNotFound < ParseQuerySegmentException
36
+ def to_s
37
+ "Property not found for the segment '#{self.str.to_s}'"
38
+ end
39
+ end
40
+
41
+ class NavigationPropertyNotFound < ParseQuerySegmentException
42
+ def to_s
43
+ "NavigationProperty not found for the segment '#{self.str.to_s}'"
44
+ end
45
+ end
46
+
47
+ class AbstractQuerySegmentException < AbstractQueryException
48
+ attr_reader :segment
49
+
50
+ def initialize(query, segment)
51
+ super(query)
52
+
53
+ @segment = segment
54
+ end
55
+ end
56
+
57
+ class ExecutionOfSegmentFailedValidation < AbstractQuerySegmentException
58
+ def to_s
59
+ "Execution of the segment '#{@segment.value}' did not return a valid result."
60
+ end
61
+ end
62
+
63
+ class InvalidSegmentContext < AbstractQuerySegmentException
64
+ def to_s
65
+ "Invalid context for the segment '#{@segment.value}'."
66
+ end
67
+ end
68
+
69
+ class AbstractQueryKeyValueException < AbstractQueryException
70
+ attr_reader :key, :value
71
+
72
+ def initialize(query, key, value)
73
+ super(query)
74
+
75
+ @key = key
76
+ @value = value
77
+ end
78
+
79
+ def to_s
80
+ "An unknown error has occured for the query option '#{@key.to_s}'."
81
+ end
82
+ end
83
+
84
+ class InvalidReservedOptionName < AbstractQueryKeyValueException
85
+ def to_s
86
+ "The query parameter '#{self.key.to_s}' begins with a system-reserved '$' character but is not recognized."
87
+ end
88
+ end
89
+
90
+ class AbstractQueryOptionException < AbstractQueryException
91
+ attr_reader :option
92
+
93
+ def initialize(query, option)
94
+ super(query)
95
+
96
+ @option = option
97
+ end
98
+
99
+ def to_s
100
+ "Invalid '#{@option.option_name.to_s}' query option."
101
+ end
102
+ end
103
+
104
+ class InvalidOptionContext < AbstractQueryOptionException
105
+ def to_s
106
+ "Invalid context for the query option '#{self.option.option_name.to_s}'."
107
+ end
108
+ end
109
+
110
+ class InvalidOptionValue < AbstractQueryOptionException
111
+ def to_s
112
+ "Invalid value for '#{self.option.option_name.to_s}' query option#{" - the only acceptable values are #{self.option.valid_values.collect { |v| v.to_s.inspect }.to_sentence}" if self.option.respond_to?(:valid_values)}."
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,58 @@
1
+ module OData
2
+ module AbstractQuery
3
+ class BasicOption
4
+ def self.option_name(option)
5
+ nil
6
+ end
7
+
8
+ def option_name
9
+ @key
10
+ end
11
+
12
+ attr_reader :query, :key, :value
13
+
14
+ def initialize(query, key, value = nil)
15
+ @query = query
16
+ @key = key
17
+ @value = value
18
+ end
19
+
20
+ def inspect
21
+ "#<< #{@query.schema.namespace.to_s}(#{@key.to_s}=#{@value.to_s}) >>"
22
+ end
23
+ end
24
+
25
+ class Option < BasicOption
26
+ def self.option_name
27
+ name.to_s.demodulize.sub(/Option$/, '')
28
+ end
29
+
30
+ def option_name
31
+ self.class.option_name
32
+ end
33
+
34
+ def initialize(query, key, value = nil)
35
+ super(query, key, value)
36
+
37
+ raise Errors::InvalidOptionContext.new(self.query, self) unless applies_to?
38
+ raise Errors::InvalidOptionValue.new(self.query, self) unless valid?
39
+ end
40
+
41
+ def self.applies_to?(query)
42
+ false
43
+ end
44
+
45
+ def self.parse!(query, key, value = nil)
46
+ nil
47
+ end
48
+
49
+ def applies_to?
50
+ self.class.applies_to?(self.query)
51
+ end
52
+
53
+ def valid?
54
+ true
55
+ end
56
+ end
57
+ end
58
+ end