Commit | Line | Data |
532cc23b |
1 | package stemmaweb::Controller::Stemweb; |
2 | use Moose; |
3 | use namespace::autoclean; |
4 | use JSON qw/ from_json /; |
70744367 |
5 | use LWP::UserAgent; |
532cc23b |
6 | use Safe::Isa; |
7 | use TryCatch; |
70744367 |
8 | use URI; |
532cc23b |
9 | |
10 | BEGIN { extends 'Catalyst::Controller' } |
11 | |
70744367 |
12 | ## TODO Move the /algorithms/available function to the Stemweb module |
13 | my $STEMWEB_BASE_URL = 'http://slinkola.users.cs.helsinki.fi'; |
14 | |
532cc23b |
15 | =head1 NAME |
16 | |
17 | stemmaweb::Controller::Stemweb - Client listener for Stemweb results |
18 | |
19 | =head1 DESCRIPTION |
20 | |
21 | This is a client listener for the Stemweb API as implemented by the protocol defined at |
22 | L<https://docs.google.com/document/d/1aNYGAo1v1WPDZi6LXZ30FJSMJwF8RQPYbOkKqHdCZEc/pub>. |
23 | |
24 | =head1 METHODS |
25 | |
26 | =head2 result |
27 | |
28 | POST stemweb/result |
29 | Content-Type: application/json |
30 | (On success): |
31 | { job_id: <ID number> |
32 | status: 0 |
33 | format: <format> |
34 | result: <data> } |
35 | (On failure): |
36 | { jobid: <ID number> |
37 | status: >1 |
38 | result: <error message> } |
39 | |
40 | Used by the Stemweb server to notify us that one or more stemma graphs |
41 | has been calculated in response to an earlier request. |
42 | |
43 | =cut |
44 | |
45 | sub result :Local :Args(0) { |
46 | my( $self, $c ) = @_; |
47 | if( $c->request->method eq 'POST' ) { |
48 | # TODO: Verify the sender! |
49 | my $answer; |
50 | if( ref( $c->request->body ) eq 'File::Temp' ) { |
51 | # Read in the file and parse that. |
52 | open( POSTDATA, $c->request->body ) or die "Failed to open post data file"; |
53 | binmode( POSTDATA, ':utf8' ); |
54 | # JSON should be all one line |
55 | my $pdata = <POSTDATA>; |
56 | chomp $pdata; |
57 | close POSTDATA; |
58 | $answer = from_json( $pdata ); |
59 | } else { |
60 | $answer = from_json( $c->request->body ); |
61 | } |
62 | # Find a tradition with the defined Stemweb job ID. |
63 | # TODO: Maybe get Stemweb to pass back the tradition ID... |
64 | my $m = $c->model('Directory'); |
65 | my @traditions; |
66 | $m->scan( sub{ push( @traditions, $_[0] ) |
67 | if $_[0]->$_isa('Text::Tradition') |
68 | && $_[0]->has_stemweb_jobid |
69 | && $_[0]->stemweb_jobid eq $answer->{job_id}; |
70 | } ); |
71 | if( @traditions == 1 ) { |
72 | my $tradition = shift @traditions; |
73 | if( $answer->{status} == 0 ) { |
74 | try { |
75 | $tradition->record_stemweb_result( $answer ); |
76 | $m->save( $tradition ); |
77 | } catch( Text::Tradition::Error $e ) { |
78 | return _json_error( $c, 500, $e->message ); |
79 | } catch { |
80 | return _json_error( $c, 500, $@ ); |
81 | } |
82 | # If we got here, success! |
83 | $c->stash->{'result'} = { 'status' => 'success' }; |
84 | $c->forward('View::JSON'); |
85 | } else { |
86 | return _json_error( $c, 500, |
87 | "Stemweb failure not handled: " . $answer->{result} ); |
88 | } |
89 | } elsif( @traditions ) { |
90 | return _json_error( $c, 500, |
91 | "Multiple traditions with Stemweb job ID " . $answer->{job_id} . "!" ); |
92 | } else { |
93 | return _json_error( $c, 400, |
94 | "No tradition found with Stemweb job ID " . $answer->{job_id} ); |
95 | } |
96 | } else { |
97 | return _json_error( $c, 403, 'Please use POST!' ); |
98 | } |
99 | } |
100 | |
70744367 |
101 | =head2 request |
102 | |
103 | GET stemweb/request/? |
104 | tradition=<tradition ID> & |
105 | algorithm=<algorithm ID> & |
106 | [<algorithm parameters>] |
107 | |
108 | Send a request for the given tradition with the given parameters to Stemweb. |
109 | Processes and returns the JSON response given by the Stemweb server. |
110 | |
111 | =cut |
112 | |
113 | sub request :Local :Args(0) { |
114 | my( $self, $c ) = @_; |
115 | # Look up the relevant tradition and check permissions. |
116 | my $reqparams = $c->req->params; |
117 | my $tid = delete $reqparams->{tradition}; |
118 | my $t = $c->model('Directory')->tradition( $tid ); |
119 | my $ok = _check_permission( $c, $t ); |
120 | return unless $ok; |
121 | return( _json_error( $c, 403, |
122 | 'You do not have permission to update stemmata for this tradition' ) ) |
123 | unless $ok eq 'full'; |
124 | |
125 | # Form the request for Stemweb. |
126 | my $algorithm = delete $reqparams->{algorithm}; |
127 | my $return_uri = URI->new( $c->uri_for( '/stemweb/result' ) ); |
128 | my $stemweb_request = { |
129 | return_path => $return_uri->path, |
130 | return_host => $return_uri->host_port, |
131 | data => $t->collation->as_tsv, |
132 | userid => $c->user->email, |
133 | parameters => $reqparams }; |
134 | |
135 | # Call to the appropriate URL with the request parameters. |
136 | my $ua = LWP::UserAgent->new(); |
137 | my $resp = $ua->post( $STEMWEB_BASE_URL . "/algorithms/process/$algorithm/", |
138 | 'Content-Type' => 'application/json; charset=utf-8', |
139 | 'Content' => encode_json( $stemweb_request ) ); |
140 | if( $resp->is_success ) { |
141 | # Process it |
142 | my $stemweb_response = decode_json( $resp->content ); |
143 | try { |
144 | $t->set_stemweb_jobid( $stemweb_response->{jobid} ); |
145 | } catch( Text::Tradition::Error $e ) { |
146 | return _json_error( $c, 429, $e->message ); |
147 | } |
148 | $c->model('Directory')->save( $t ); |
149 | $c->stash->{'result'} = $stemweb_response; |
150 | $c->forward('View::JSON'); |
151 | } elsif( $resp->code == 500 && $resp->header('Client-Warning') |
152 | && $resp->header('Client-Warning') eq 'Internal response' ) { |
153 | # The server was unavailable. |
154 | return _json_error( $c, 503, "The Stemweb server is currently unreachable." ); |
155 | } else { |
156 | return _json_error( $c, 500, "Stemweb error: " . $resp->status . " / " |
157 | . $resp->content ); |
158 | } |
159 | } |
160 | |
161 | # Helper to check what permission, if any, the active user has for |
162 | # the given tradition |
163 | sub _check_permission { |
164 | my( $c, $tradition ) = @_; |
165 | my $user = $c->user_exists ? $c->user->get_object : undef; |
166 | if( $user ) { |
167 | return 'full' if ( $user->is_admin || |
168 | ( $tradition->has_user && $tradition->user->id eq $user->id ) ); |
169 | } |
170 | # Text doesn't belong to us, so maybe it's public? |
171 | return 'readonly' if $tradition->public; |
172 | |
173 | # ...nope. Forbidden! |
174 | return _json_error( $c, 403, 'You do not have permission to view this tradition.' ); |
175 | } |
176 | |
532cc23b |
177 | # Helper to throw a JSON exception |
178 | sub _json_error { |
179 | my( $c, $code, $errmsg ) = @_; |
180 | $c->response->status( $code ); |
181 | $c->stash->{'result'} = { 'error' => $errmsg }; |
182 | $c->forward('View::JSON'); |
183 | return 0; |
184 | } |
185 | |
186 | 1; |