Commit | Line | Data |
0f093b35 |
1 | #!/usr/bin/perl -T |
2 | |
3 | use strict; |
4 | use warnings; |
0f093b35 |
5 | use CGI; |
6 | use Encode qw/ decode /; |
7 | use Gearman::Client; |
8 | use JSON; |
9 | use Text::Tradition::Directory; |
10 | use Text::Tradition::Analysis::Result; |
11 | use TryCatch; |
12 | |
58ad1c17 |
13 | ### Configurable variables |
d1dfc40d |
14 | my %VARS = ( |
15 | DBTYPE => 'mysql', |
16 | DBHOST => '127.0.0.1', |
17 | DBPORT => '3006', |
18 | DBNAME => 'idpresult', |
19 | DSN => undef, |
20 | DBUSER => undef, |
21 | DBPASS => undef, |
22 | GEARMAN_SERVER => '127.0.0.1:4730', |
23 | ); |
24 | |
25 | if( -f "/etc/graphcalc.conf" ) { |
26 | # Read the variables in from here. |
27 | open( GCCONF, "/etc/graphcalc.conf" ) |
28 | or die "Could not open configuration file /etc/graphcalc.conf"; |
29 | while(<GCCONF>) { |
30 | chomp; |
31 | s/^\s+//; |
32 | my( $name, $val ) = split( /\s*\=\s*/, $_ ); |
33 | if( exists $VARS{$name} ) { |
34 | $VARS{$name} = $val; |
35 | } |
36 | } |
37 | close GCCONF; |
38 | } |
39 | unless( $VARS{DSN} ) { |
40 | $VARS{DSN} = sprintf( "dbi:%s:dbname=%s;host=%s;port=%s", |
41 | $VARS{DBTYPE}, $VARS{DBNAME}, $VARS{DBHOST}, $VARS{DBPORT} ); |
42 | } |
58ad1c17 |
43 | |
44 | ### Main program |
45 | |
0f093b35 |
46 | my %status = ( |
47 | '400' => '400 Bad Request', |
48 | '500' => '500 Internal Server Error', |
49 | ); |
50 | |
51 | my $q = CGI->new(\*STDIN); |
52 | # check that Content-Type is application/json |
53 | my $ctype = $q->content_type; |
54 | my $encoding = 'UTF-8'; # default |
55 | if( $ctype =~ m!^(\w+/[\w+]+);\s*charset=(.*)$! ) { |
56 | ( $ctype, $encoding ) = ( $1, $2 ); |
57 | } |
58 | error( 400, 'Content type must be application/json' ) |
59 | unless $ctype eq 'application/json'; |
60 | |
61 | # Get the post data, and decode it according to the given character set |
62 | my $jsonstr = decode( $encoding, $q->param('POSTDATA') ); |
63 | $jsonstr =~ s/\&/\n/g; |
0f839794 |
64 | # Validate the JSON |
0f093b35 |
65 | my $request; |
66 | try { |
67 | $request = from_json( $jsonstr ); |
68 | } catch( $err ) { |
69 | error( 400, "JSON parsing error: $err" ); |
70 | } |
71 | # Request should be a hash that can be used to instantiate an Analysis::Result, |
72 | # or else an array of such hashes. |
73 | my @problems; |
74 | my $first = ref( $request ) eq 'ARRAY' ? shift @$request : $request; |
75 | try { |
76 | my $result = Text::Tradition::Analysis::Result->new( $first ); |
77 | push( @problems, $result ); |
78 | } catch( $err ) { |
79 | error( 400, "Argument $first is neither a Result serialization nor an array: $err" ); |
80 | } |
81 | # Now parse the rest of the result objects |
82 | unless( $first eq $request ) { |
83 | foreach my $rj ( @$request ) { |
84 | try { |
85 | my $result = Text::Tradition::Analysis::Result->new( $rj ); |
86 | push( @problems, $result ); |
87 | } catch( $err ) { |
88 | error( 400, "Argument $rj is not a Result serialization: $err" ); |
89 | } |
90 | } |
91 | } |
92 | |
93 | # For each of the result objects, see if its key exists in the DB. Kick off the |
94 | # calculation of any that need to be calculated, but don't wait more than two |
95 | # seconds for a result. Return the DB version of each of the objects. |
58ad1c17 |
96 | my $dbargs = {}; |
d1dfc40d |
97 | $dbargs->{user} = $VARS{DBUSER} if $VARS{DBUSER}; |
98 | $dbargs->{password} = $VARS{DBPASS} if $VARS{DBPASS}; |
99 | my $dir = Text::Tradition::Directory->new( |
100 | 'dsn' => $VARS{DSN}, 'extra_args' => $dbargs ); |
0f093b35 |
101 | my $scope = $dir->new_scope; |
102 | my %results; |
103 | my @resultorder; # Keep track of the order in which we should return the results |
104 | my @needcalc; |
105 | foreach my $p ( @problems ) { |
106 | my $key = $p->object_key; |
107 | push( @resultorder, $key ); |
108 | my $result = $dir->lookup( $key ); |
109 | if( $result ) { |
110 | $results{$key} = $result; |
111 | } else { |
112 | push( @needcalc, $p ); |
113 | $dir->store( $key => $p ); |
114 | } |
115 | } |
116 | |
117 | # Now if any of the results need calculation, dispatch them for the purpose. |
118 | if( @needcalc ) { |
119 | my $arg = join( ',', map { $_->object_key } @needcalc ); |
120 | my $client = Gearman::Client->new; |
d1dfc40d |
121 | $client->job_servers( $VARS{GEARMAN_SERVER} ); |
0f093b35 |
122 | my $task = $client->dispatch_background( run_idp => $arg ); |
123 | # See if it finishes quickly |
124 | my $wait = 3; |
125 | sleep( $wait ); |
126 | # Now replace the problems in the results hash with the DB results, |
127 | # whether finished or still calculating. |
128 | foreach my $p ( @needcalc ) { |
129 | # this should NOT fail as we stored it above |
130 | $results{$p->object_key} = $dir->lookup( $p->object_key ); |
131 | } |
132 | } |
133 | |
134 | # Finally, assemble our answer. |
135 | my $answer; |
136 | if( $first eq $request ) { |
137 | $answer = $results{$resultorder[0]}; |
138 | } else { |
139 | foreach my $key ( @resultorder ) { |
140 | push( @$answer, $results{$key} ); |
141 | } |
142 | } |
143 | |
144 | |
145 | # Now return the response as UTF-8 encoded JSON. |
146 | print $q->header(-type => 'application/json', -charset => 'UTF-8' ); |
147 | print JSON->new->allow_blessed->convert_blessed->utf8->encode( $answer ); |
148 | exit 0; |
149 | |
150 | sub error { |
151 | my( $code, $msg ) = @_; |
152 | print $q->header( -type => 'text/plain', -charset => 'UTF-8', -status => $status{$code} ); |
153 | print "$msg\n"; |
154 | exit 0; |
155 | } |