switch to opentox-ruby version 4.0.0
[task] / application.rb
1 require 'rubygems'
2 gem "opentox-ruby", "~> 4"
3 require 'opentox-ruby'
4
5 set :lock, true
6
7 class Task < Ohm::Model
8         
9         attribute :uri
10   attribute :created_at
11
12   attribute :finished_at
13   attribute :due_to_time
14   attribute :pid
15
16   attribute :resultURI
17   attribute :percentageCompleted
18   attribute :hasStatus
19   attribute :title
20   attribute :creator
21   attribute :description
22
23   attribute :waiting_for
24
25   attribute :error_report_yaml
26   
27   # convenience method to store object in redis
28   def errorReport
29     YAML.load(self.error_report_yaml) if self.error_report_yaml
30   end
31
32   # convenience method to store object in redis
33   def errorReport=(er)
34     self.error_report_yaml = er.to_yaml
35   end
36   
37
38   def metadata
39     {
40       DC.creator => creator,
41       DC.title => title,
42       DC.date => created_at,
43       OT.hasStatus => hasStatus,
44       OT.resultURI => resultURI,
45       OT.percentageCompleted => percentageCompleted.to_f,
46       #text fields are lazy loaded, using member variable can cause description to be nil
47       DC.description => description   
48       #:due_to_time => @due_to_timer
49     }
50   end
51
52 end
53
54 #DataMapper.auto_upgrade!
55
56 # Get a list of all tasks
57 # @return [text/uri-list] List of all tasks
58 get '/?' do
59         LOGGER.debug "list all tasks "+params.inspect
60   if request.env['HTTP_ACCEPT'] =~ /html/
61     response['Content-Type'] = 'text/html'
62     OpenTox.text_to_html Task.all.sort.collect{|t| t.uri}.join("\n") + "\n"
63   else
64     response['Content-Type'] = 'text/uri-list'
65     Task.all.collect{|t| t.uri}.join("\n") + "\n"
66   end
67 end
68
69 # Get task representation
70 # @param [Header] Accept Mime type of accepted representation, may be one of `application/rdf+xml,application/x-yaml,text/uri-list`
71 # @return [application/rdf+xml,application/x-yaml,text/uri-list] Task representation in requested format, Accept:text/uri-list returns URI of the created resource if task status is "Completed"
72 get '/:id/?' do
73   task = Task[params[:id]]
74   raise OpenTox::NotFoundError.new "Task '#{params[:id]}' not found." unless task
75   
76   # set task http code according to status
77   case task.hasStatus
78   when "Running"
79     code = 202
80   when "Cancelled"
81     code = 503
82   when "Error"
83     if task.errorReport
84        code = task.errorReport.http_code.to_i
85     else
86      code = 500
87     end
88   else #Completed
89     code = 200
90   end
91   
92   case request.env['HTTP_ACCEPT']
93   when /yaml/ 
94     response['Content-Type'] = 'application/x-yaml'
95     metadata = task.metadata
96     metadata[OT.waitingFor] = task.waiting_for
97     metadata[OT.errorReport] = task.errorReport if task.errorReport
98     halt code, metadata.to_yaml
99     #halt code, task.created_at
100   when /html/
101     response['Content-Type'] = 'text/html'
102     metadata = task.metadata
103     description = task.title ? "This task computes '"+task.title+"'" : "This task performs a process that is running on the server."
104     if task.hasStatus=="Running"
105       description << "\nRefresh your browser (presss F5) to see if the task has finished."
106     elsif task.hasStatus=="Completed"
107       description << "\nThe task is completed, click on the link below to see your result."
108     elsif task.errorReport
109       description << "\nUnfortunately, the task has failed."
110     end
111     related_links = task.hasStatus=="Completed" ? "The task result: "+task.resultURI : nil
112     metadata[OT.waitingFor] = task.waiting_for
113     metadata[OT.errorReport] = task.errorReport if task.errorReport
114     halt code, OpenTox.text_to_html(metadata.to_yaml, @subjectid, related_links, description)    
115   when /application\/rdf\+xml|\*\/\*/ # matches 'application/x-yaml', '*/*'
116     response['Content-Type'] = 'application/rdf+xml'
117     t = OpenTox::Task.new task.uri
118     t.add_metadata task.metadata
119     t.add_error_report task.errorReport if task.errorReport
120     halt code, t.to_rdfxml
121   when /text\/uri\-list/
122     response['Content-Type'] = 'text/uri-list'
123     if task.hasStatus=="Completed"
124       halt code, task.resultURI
125     else
126       halt code, task.uri
127     end
128   else
129     raise OpenTox::BadRequestError.new "MIME type '"+request.env['HTTP_ACCEPT'].to_s+"' not supported, valid Accept-Headers are \"application/rdf+xml\" and \"application/x-yaml\"."
130   end
131 end
132
133
134 # Get Task properties. Works for
135 # - /task/id
136 # - /task/uri
137 # - /task/created_at
138 # - /task/finished_at
139 # - /task/due_to_time
140 # - /task/pid
141 # - /task/resultURI
142 # - /task/percentageCompleted
143 # - /task/hasStatus
144 # - /task/title
145 # - /task/creator
146 # - /task/description
147 # @return [String] Task property
148 get '/:id/:property/?' do
149         response['Content-Type'] = 'text/plain'
150   task = Task[params[:id]]
151   raise OpenTox::NotFoundError.new"Task #{params[:id]} not found." unless task
152   begin
153     eval("task.#{params[:property]}").to_s
154   rescue
155     raise OpenTox::NotFoundError.new"Unknown task property #{params[:property]}."
156   end
157 end
158
159 # Create a new task
160 # @param [optional,String] max_duration
161 # @param [optional,String] pid
162 # @param [optional,String] resultURI
163 # @param [optional,String] percentageCompleted
164 # @param [optional,String] hasStatus
165 # @param [optional,String] title
166 # @param [optional,String] creator
167 # @param [optional,String] description
168 # @return [text/uri-list] URI for new task
169 post '/?' do
170   LOGGER.debug "Creating new task with params "+params.inspect
171   max_duration = params.delete(:max_duration.to_s) if params.has_key?(:max_duration.to_s)
172   params[:created_at] = Time.now
173   params[:hasStatus] = "Running" unless params[:hasStatus]
174   task = Task.create params
175   task.update :uri => url_for("/#{task.id}", :full)
176   #task.due_to_time = DateTime.parse((Time.parse(task.created_at.to_s) + max_duration.to_f).to_s) if max_duration
177   #raise "Could not save task #{task.uri}" unless task.save
178   response['Content-Type'] = 'text/uri-list'
179   task.uri + "\n"
180 end
181
182 # Clean tasks. Delete every completed task older than 30 days
183 delete '/cleanup' do
184   begin
185     tasklist = Task.all
186     tasklist.each do |task|
187       if task.metadata[OT.hasStatus] == 'Completed'
188         task.delete if Time.now - Time.parse(task.created_at) > 2592000
189       end
190     end
191   rescue
192     return false
193   end
194   return true
195 end
196
197 # Change task status. Possible URIs are: `
198 # - /task/Cancelled
199 # - /task/Completed: requires taskURI argument
200 # - /task/Running
201 # - /task/Error
202 # - /task/pid: requires pid argument
203 # IMPORTANT NOTE: Rack does not accept empty PUT requests. Please send an empty parameter (e.g. with -d '' for curl) or you will receive a "411 Length Required" error.
204 # @param [optional, String] resultURI URI of created resource, required for /task/Completed
205 # @param [optional, String] pid Task PID, required for /task/pid
206 # @param [optional, String] description Task description
207 # @param [optional, String] percentageCompleted progress value, can only be set while running
208 # @return [] nil
209 put '/:id/:hasStatus/?' do
210   
211         task = Task[params[:id]]
212   raise OpenTox::NotFoundError.new"Task #{params[:id]} not found." unless task
213         task.hasStatus = params[:hasStatus] unless /pid/ =~ params[:hasStatus]
214   task.description = params[:description] if params[:description]
215   # error report comes as yaml string
216   task.error_report_yaml = params[:errorReport] if params[:errorReport]
217   
218         case params[:hasStatus]
219         when "Completed"
220                 LOGGER.debug "Task " + params[:id].to_s + " completed"
221     raise OpenTox::BadRequestError.new"no param resultURI when completing task" unless params[:resultURI]
222     task.resultURI = params[:resultURI]
223                 task.finished_at = DateTime.now
224     task.percentageCompleted = 100
225     task.pid = nil
226   when "pid"
227     task.pid = params[:pid]
228   when "Running"
229     raise OpenTox::BadRequestError.new"Task cannot be set to running after not running anymore" if task.hasStatus!="Running"
230     task.waiting_for = params[:waiting_for] if params.has_key?("waiting_for")
231     if params.has_key?("percentageCompleted")
232       task.percentageCompleted = params[:percentageCompleted].to_f
233       #LOGGER.debug "Task " + params[:id].to_s + " set percentage completed to: "+params[:percentageCompleted].to_s
234     end
235         when /Cancelled|Error/
236     if task.waiting_for and task.waiting_for.uri?
237       # try cancelling the child task
238       begin
239         w = OpenTox::Task.find(task.waiting_for)
240         w.cancel if w.running?
241       rescue
242       end
243     end
244     LOGGER.debug("Aborting task '"+task.uri.to_s+"' with pid: '"+task.pid.to_s+"'")
245                 Process.kill(9,task.pid.to_i) unless task.pid.nil?
246                 task.pid = nil
247   else
248      raise OpenTox::BadRequestError.new"Invalid value for hasStatus: '"+params[:hasStatus].to_s+"'"
249   end
250         
251   raise"could not save task" unless task.save
252 end
253
254 # Delete a task
255 # @return [text/plain] Status message
256 delete '/:id/?' do
257         task = Task[params[:id]]
258   raise OpenTox::NotFoundError.new "Task #{params[:id]} not found." unless task
259         begin
260                 Process.kill(9,task.pid) unless task.pid.nil?
261     task.delete
262     response['Content-Type'] = 'text/plain'
263     "Task #{params[:id]} deleted."
264         rescue
265                 raise"Cannot kill task with pid #{task.pid}"
266         end
267 end
268
269 # Delete all tasks
270 # @return [text/plain] Status message
271 delete '/?' do
272         Task.all.each do |task|
273                 begin
274                         Process.kill(9,task.pid.to_i) unless task.pid.nil?
275       task.delete
276       response['Content-Type'] = 'text/plain'
277       "All tasks deleted."
278                 rescue
279                         "Cannot kill task with pid #{task.pid}"
280                 end
281         end
282 end