api06: Move version-checking into the models, raising an exception on mismatch
[rails.git] / app / controllers / relation_controller.rb
1 class RelationController < ApplicationController
2   require 'xml/libxml'
3
4   session :off
5   before_filter :authorize, :only => [:create, :update, :delete]
6   before_filter :check_write_availability, :only => [:create, :update, :delete]
7   before_filter :check_read_availability, :except => [:create, :update, :delete]
8   after_filter :compress_output
9
10   def create
11     if request.put?
12       relation = Relation.from_xml(request.raw_post, true)
13
14       if relation
15         if !relation.preconditions_ok?
16           render :text => "", :status => :precondition_failed
17         else
18           relation.version = 0
19           relation.user_id = @user.id
20           relation.save_with_history!
21
22           render :text => relation.id.to_s, :content_type => "text/plain"
23         end
24       else
25         render :nothing => true, :status => :bad_request
26       end
27     else
28       render :nothing => true, :status => :method_not_allowed
29     end
30   end
31
32   def read
33     begin
34       relation = Relation.find(params[:id])
35       response.headers['Last-Modified'] = relation.timestamp.rfc822
36       if relation.visible
37         render :text => relation.to_xml.to_s, :content_type => "text/xml"
38       else
39         render :text => "", :status => :gone
40       end
41     rescue ActiveRecord::RecordNotFound
42       render :nothing => true, :status => :not_found
43     rescue
44       render :nothing => true, :status => :internal_server_error
45     end
46   end
47
48   def update
49     begin
50       relation = Relation.find(params[:id])
51       new_relation = Relation.from_xml(request.raw_post)
52       if new_relation.version != relation.version
53         render :text => "Version mismatch: Provided " + new_relation.version.to_s + ", server had: " + relation.version.to_s, :status => :bad_request
54         return
55       end  
56
57       if new_relation and new_relation.id == relation.id
58         relation.update_from new_relation, user
59         render :text => relation.version.to_s, :content_type => "text/plain"
60       else
61         render :nothing => true, :status => :bad_request
62       end
63     rescue ActiveRecord::RecordNotFound
64       render :nothing => true, :status => :not_found
65     rescue OSM::APIPreconditionFailedError
66       render :text => "", :status => :precondition_failed
67     rescue OSM::APIVersionMismatchError => ex
68       render :text => "Version mismatch: Provided " + ex.provided.to_s +
69         ", server had: " + ex.latest.to_s, :status => :bad_request
70     rescue
71       render :nothing => true, :status => :internal_server_error
72     end
73   end
74
75   def delete
76 #XXX check if member somewhere!
77     begin
78       relation = Relation.find(params[:id])
79       relation.delete_with_history(@user)
80     rescue OSM::APIAlreadyDeletedError
81       render :text => "", :status => :gone
82     rescue OSM::APIPreconditionFailedError
83       render :text => "", :status => :precondition_failed
84     rescue ActiveRecord::RecordNotFound
85       render :nothing => true, :status => :not_found
86     rescue
87       render :nothing => true, :status => :internal_server_error
88     end
89   end
90
91   # -----------------------------------------------------------------
92   # full
93   # 
94   # input parameters: id
95   #
96   # returns XML representation of one relation object plus all its
97   # members, plus all nodes part of member ways
98   # -----------------------------------------------------------------
99   def full
100     begin
101       relation = Relation.find(params[:id])
102
103       if relation.visible
104
105         # first collect nodes, ways, and relations referenced by this relation.
106         
107         ways = Way.find_by_sql("select w.* from current_ways w,current_relation_members rm where "+
108             "rm.member_type='way' and rm.member_id=w.id and rm.id=#{relation.id}");
109         nodes = Node.find_by_sql("select n.* from current_nodes n,current_relation_members rm where "+
110             "rm.member_type='node' and rm.member_id=n.id and rm.id=#{relation.id}");
111         # note query is built to exclude self just in case.
112         relations = Relation.find_by_sql("select r.* from current_relations r,current_relation_members rm where "+
113             "rm.member_type='relation' and rm.member_id=r.id and rm.id=#{relation.id} and r.id<>rm.id");
114
115         # now additionally collect nodes referenced by ways. Note how we recursively 
116         # evaluate ways but NOT relations.
117
118         node_ids = nodes.collect {|node| node.id }
119         way_node_ids = ways.collect { |way|
120            way.way_nodes.collect { |way_node| way_node.node_id }
121         }
122         way_node_ids.flatten!
123         way_node_ids.uniq
124         way_node_ids -= node_ids
125         nodes += Node.find(way_node_ids)
126     
127         # create XML.
128         doc = OSM::API.new.get_xml_doc
129         visible_nodes = {}
130         user_display_name_cache = {}
131
132         nodes.each do |node|
133           if node.visible? # should be unnecessary if data is consistent.
134             doc.root << node.to_xml_node(user_display_name_cache)
135             visible_nodes[node.id] = node
136           end
137         end
138         ways.each do |way|
139           if way.visible? # should be unnecessary if data is consistent.
140             doc.root << way.to_xml_node(visible_nodes, user_display_name_cache)
141           end
142         end
143         relations.each do |rel|
144           if rel.visible? # should be unnecessary if data is consistent.
145             doc.root << rel.to_xml_node(user_display_name_cache)
146           end
147         end
148         # finally add self and output
149         doc.root << relation.to_xml_node(user_display_name_cache)
150         render :text => doc.to_s, :content_type => "text/xml"
151
152       else
153
154         render :text => "", :status => :gone
155       end
156
157     rescue ActiveRecord::RecordNotFound
158       render :nothing => true, :status => :not_found
159
160     rescue
161       render :nothing => true, :status => :internal_server_error
162     end
163   end
164
165   def relations
166     ids = params['relations'].split(',').collect { |w| w.to_i }
167
168     if ids.length > 0
169       doc = OSM::API.new.get_xml_doc
170
171       Relation.find(ids).each do |relation|
172         doc.root << relation.to_xml_node
173       end
174
175       render :text => doc.to_s, :content_type => "text/xml"
176     else
177       render :nothing => true, :status => :bad_request
178     end
179   end
180
181   def relations_for_way
182     relations_for_object("way")
183   end
184   def relations_for_node
185     relations_for_object("node")
186   end
187   def relations_for_relation
188     relations_for_object("relation")
189   end
190
191   def relations_for_object(objtype)
192     relationids = RelationMember.find(:all, :conditions => ['member_type=? and member_id=?', objtype, params[:id]]).collect { |ws| ws.id }.uniq
193
194     doc = OSM::API.new.get_xml_doc
195
196     Relation.find(relationids).each do |relation|
197       doc.root << relation.to_xml_node
198     end
199
200     render :text => doc.to_s, :content_type => "text/xml"
201   end
202 end