odata_server 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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