Merge branch 'master' into 1971-show-image-thumbnails
[arvados.git] / services / api / app / controllers / arvados / v1 / collections_controller.rb
1 class Arvados::V1::CollectionsController < ApplicationController
2   def create
3     # Collections are owned by system_user. Creating a collection has
4     # two effects: The collection is added if it doesn't already
5     # exist, and a "permission" Link is added (if one doesn't already
6     # exist) giving the current user (or specified owner_uuid)
7     # permission to read it.
8     owner_uuid = resource_attrs.delete(:owner_uuid) || current_user.uuid
9     unless current_user.can? write: owner_uuid
10       logger.warn "User #{current_user.andand.uuid} tried to set collection owner_uuid to #{owner_uuid}"
11       raise ArvadosModel::PermissionDeniedError
12     end
13     act_as_system_user do
14       @object = model_class.new resource_attrs.reject { |k,v| k == :owner_uuid }
15       begin
16         @object.save!
17       rescue ActiveRecord::RecordNotUnique
18         logger.debug resource_attrs.inspect
19         if resource_attrs[:manifest_text] and resource_attrs[:uuid]
20           @existing_object = model_class.
21             where('uuid=? and manifest_text=?',
22                   resource_attrs[:uuid],
23                   resource_attrs[:manifest_text]).
24             first
25           @object = @existing_object || @object
26         end
27       end
28
29       if @object
30         link_attrs = {
31           owner_uuid: owner_uuid,
32           link_class: 'permission',
33           name: 'can_read',
34           head_uuid: @object.uuid,
35           tail_uuid: owner_uuid
36         }
37         ActiveRecord::Base.transaction do
38           if Link.where(link_attrs).empty?
39             Link.create! link_attrs
40           end
41         end
42       end
43     end
44     show
45   end
46
47   def show
48     render json: @object.as_api_response(:with_data)
49   end
50
51   def collection_uuid(uuid)
52     m = /([a-f0-9]{32}(\+[0-9]+)?)(\+.*)?/.match(uuid)
53     if m
54       m[1]
55     else
56       nil
57     end
58   end
59
60   def script_param_edges(visited, sp)
61     case sp
62     when Hash
63       sp.each do |k, v|
64         script_param_edges(visited, v)
65       end
66     when Array
67       sp.each do |v|
68         script_param_edges(visited, v)
69       end
70     when String
71       return if sp.empty?
72       m = collection_uuid(sp)
73       if m
74         generate_provenance_edges(visited, m)
75       end
76     end
77   end
78
79   def generate_provenance_edges(visited, uuid)
80     m = collection_uuid(uuid)
81     uuid = m if m
82
83     if not uuid or uuid.empty? or visited[uuid]
84       return ""
85     end
86
87     logger.debug "visiting #{uuid}"
88
89     if m  
90       # uuid is a collection
91       Collection.readable_by(current_user).where(uuid: uuid).each do |c|
92         visited[uuid] = c.as_api_response
93         visited[uuid][:files] = []
94         c.files.each do |f|
95           visited[uuid][:files] << f
96         end
97       end
98
99       Job.readable_by(current_user).where(output: uuid).each do |job|
100         generate_provenance_edges(visited, job.uuid)
101       end
102
103       Job.readable_by(current_user).where(log: uuid).each do |job|
104         generate_provenance_edges(visited, job.uuid)
105       end
106       
107     else
108       # uuid is something else
109       rsc = ArvadosModel::resource_class_for_uuid uuid
110       if rsc == Job
111         Job.readable_by(current_user).where(uuid: uuid).each do |job|
112           visited[uuid] = job.as_api_response
113           script_param_edges(visited, job.script_parameters)
114         end
115       elsif rsc != nil
116         rsc.where(uuid: uuid).each do |r|
117           visited[uuid] = r.as_api_response
118         end
119       end
120     end
121
122     Link.readable_by(current_user).
123       where(head_uuid: uuid, link_class: "provenance").
124       each do |link|
125       visited[link.uuid] = link.as_api_response
126       generate_provenance_edges(visited, link.tail_uuid)
127     end
128
129     #puts "finished #{uuid}"
130   end
131
132   def provenance
133     visited = {}
134     generate_provenance_edges(visited, @object[:uuid])
135     render json: visited
136   end
137
138   def generate_used_by_edges(visited, uuid)
139     m = collection_uuid(uuid)
140     uuid = m if m
141
142     if not uuid or uuid.empty? or visited[uuid]
143       return ""
144     end
145
146     logger.debug "visiting #{uuid}"
147
148     if m  
149       # uuid is a collection
150       Collection.readable_by(current_user).where(uuid: uuid).each do |c|
151         visited[uuid] = c.as_api_response
152         visited[uuid][:files] = []
153         c.files.each do |f|
154           visited[uuid][:files] << f
155         end
156       end
157
158       if uuid == "d41d8cd98f00b204e9800998ecf8427e+0"
159         # special case for empty collection
160         return
161       end
162
163       Job.readable_by(current_user).where(["jobs.script_parameters like ?", "%#{uuid}%"]).each do |job|
164         generate_used_by_edges(visited, job.uuid)
165       end
166       
167     else
168       # uuid is something else
169       rsc = ArvadosModel::resource_class_for_uuid uuid
170       if rsc == Job
171         Job.readable_by(current_user).where(uuid: uuid).each do |job|
172           visited[uuid] = job.as_api_response
173           generate_used_by_edges(visited, job.output)
174         end
175       elsif rsc != nil
176         rsc.where(uuid: uuid).each do |r|
177           visited[uuid] = r.as_api_response
178         end
179       end
180     end
181
182     Link.readable_by(current_user).
183       where(tail_uuid: uuid, link_class: "provenance").
184       each do |link|
185       visited[link.uuid] = link.as_api_response
186       generate_used_by_edges(visited, link.head_uuid)
187     end
188
189     #puts "finished #{uuid}"
190   end
191
192   def used_by
193     visited = {}
194     generate_used_by_edges(visited, @object[:uuid])
195     render json: visited
196   end
197
198   protected
199   def find_object_by_uuid
200     super
201     if !@object and !params[:uuid].match(/^[0-9a-f]+\+\d+$/)
202       # Normalize the given uuid and search again.
203       hash_part = params[:uuid].match(/^([0-9a-f]*)/)[1]
204       collection = Collection.where('uuid like ?', hash_part + '+%').first
205       if collection
206         # We know the collection exists, and what its real uuid is in
207         # the database. Now, throw out @objects and repeat the usual
208         # lookup procedure. (Returning the collection at this point
209         # would bypass permission checks.)
210         @objects = nil
211         @where = { uuid: collection.uuid }
212         find_objects_for_index
213         @object = @objects.first
214       end
215     end
216   end
217
218 end