clean
[sgn.git] / lib / SGN / Controller / AJAX / Onto.pm
blob108460520ae2ba6bd842b876fc9f01d3a69732ff
1 =head1 NAME
3 SGN::Controller::AJAX::Onto - a REST controller class to provide the
4 backend for the SGN ontology browser
6 =head1 DESCRIPTION
8 Essentially provides four services: roots, children, parents, and
9 cache, that the SGN ontology browser relies on. Output is JSON, as a
10 list of hashes, that has the following keys: accession, has_children,
11 cvterm_name, cvterm_id, and for some functions, parent.
13 The ontologies that should be displayed must be configured in the configuration file (sgn.conf or sgn_local.conf for SGN). Insert a line of the following format into the conf file:
15 C<onto_root_namespaces GO (Gene Ontology), PO (Plant Ontology), SO (Sequence Ontology), PATO (Phenotype and Trait Ontology), SP (Solanaceae Ontology)>
17 where onto_root_namespaces is the conf key, "GO" the two letter code of the ontology (as it appears in the db database table), and in parenthesis is the human readable name of the ontology.
19 =head1 AUTHOR
21 Lukas Mueller <lam87@cornell.edu>
23 =head1 PUBLIC ACTIONS
25 =cut
27 package SGN::Controller::AJAX::Onto;
29 use Moose;
30 use CXGN::Chado::Cvterm;
32 use namespace::autoclean;
34 BEGIN { extends 'Catalyst::Controller::REST' }
36 __PACKAGE__->config(
37 default => 'application/json',
38 stash_key => 'rest',
39 map => { 'application/json' => 'JSON', 'text/html' => 'JSON' },
43 =head2 children
45 Public Path: /<ns>/children
47 L<Catalyst::Action::REST> action.
49 Provides a list of child terms, each child term being a hashref (or
50 equivalent) with keys accession, cvterm_name, cvterm_id, has_children,
51 and relationship.
53 =cut
55 sub children : Local : ActionClass('REST') { }
57 sub children_GET {
58 my ( $self, $c ) = @_;
59 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
61 my ($db_name, $accession) = split ":", $c->request->param('node');
63 my $db = $schema->resultset('General::Db')->search({ name => uc($db_name) })->first();
64 my $dbxref = $db->find_related('dbxrefs', { accession => $accession });
66 my $cvterm = $dbxref->cvterm;
68 my $cvrel_rs = $cvterm->children(); # returns a result set
70 my @response_list = ();
71 while (my $cvrel_row = $cvrel_rs->next()) {
72 my $relationship_node = $cvrel_row->type();
73 my $child_node = $cvrel_row->subject();
75 #only report back children of the same cv namespace
76 if ($child_node->cv_id() != $cvterm->cv_id()) {
77 next();
80 my $responsehash = $self->flatten_node($child_node, $relationship_node);
81 push @response_list, $responsehash;
83 @response_list = sort { lc $a->{cvterm_name} cmp lc $b->{cvterm_name} } @response_list;
84 $c->stash->{rest} = \@response_list;
88 =head2 parents
90 Public Path: /<ns>/parents
92 L<Catalyst::Action::REST> action.
94 Returns a list of hashes with parents information in json, a list of
95 hashrefs with the keys: accession, relationship, has_children, cvterm_id
96 and cvterm_name.
98 =cut
100 sub parents : Local : ActionClass('REST') { }
102 sub parents_GET {
103 my ($self, $c) = @_;
105 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
107 my ($db_name, $accession) = split ":", $c->request->param('node');
108 my $dbxref;
109 my %response;
110 my $db = $schema->resultset('General::Db')->search({ 'upper(name)' => uc($db_name) })->first();
111 if (!$db || !$accession) {
112 #not sure we need here to send an error key, since cache is usually called after parents (? )
113 $response{error} = "Did not pass a legal ontology term ID! ( $db_name : $accession)";
114 $c->stash->{rest} = \%response;
115 return;
117 $dbxref = $db->find_related('dbxrefs', { accession => $accession }) if $db;
118 if (!$dbxref) {
119 $response{error} = "Could not find term $db_name : $accession in the database! Check your input and try again";
120 $c->stash->{rest} = \%response;
121 return;
123 my $cvterm = $dbxref->cvterm;
125 my @response_list = ();
126 if ($cvterm) {
127 my $parents_rs = $cvterm->recursive_parents(); # returns a result set
128 while (my $parent = $parents_rs->next()) {
129 #only report back children of the same cv namespace
130 if ($parent->cv_id() != $cvterm->cv_id()) {
131 next();
133 my $responsehash = $self->flatten_node($parent, undef);
134 push @response_list, $responsehash;
136 } else {
137 $response{error} = "Could not find term $db_name : $accession in the database! Check your input and try again. THIS MAY BE AN INTERNAL DATABASE PROBLEM! Please contact sgn-feedback\@sgn.cornell.edu for help.";
138 $c->stash->{rest} = \%response;
139 return;
141 $c->stash->{rest} = \@response_list;
144 =head2 menu
146 Usage:
147 Desc:
148 Ret:
149 Args:
150 Side Effects:
151 Example:
153 =cut
155 sub menu : Local : ActionClass('REST') { }
157 sub menu_GET {
158 my $self = shift;
159 my $c = shift;
161 my $menudata = $c->config->{onto_root_namespaces};
163 print STDERR "MENUDATA: $menudata\n";
164 my @menuitems = split ",", $menudata;
166 my $menu = '<select name="cv_select">';
168 foreach my $mi (@menuitems) {
169 print STDERR "MENU ITEM: $mi\n";
170 if ($mi =~ /\s*(\w+)?\s*(.*)$/) {
171 my $value = $1;
172 my $name = $2;
174 $menu .= qq { <option value="$value">$value $name</option>\n };
178 $menu .= "</select>\n";
179 $c->stash->{rest} = [ $menu ];
186 =head2 roots
188 Public Path: /<ns>/roots
190 L<Catalyst::Action::REST> action.
192 Provides the default roots for drawing the ontology browser
194 Query/Body Params:
196 nodes: optional, a string with namespace definitions, separated by white
197 space (for example, "PO SP SO"). If not provided, will This overrides the standard
198 namespaces provided when called without arguments.
200 =cut
202 sub roots : Local : ActionClass('REST') { }
204 sub roots_GET {
205 my $self = shift;
206 my $c = shift;
207 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
209 my $namespace = $c->request->param('nodes');
210 my @namespaces = ();
211 my @response_nodes = ();
212 #my $empty_cvterm = CXGN::Chado::Cvterm->new($c->dbc()->dbh());
213 if (!$namespace) { # should namespaces be db names ? (SO, GO,PO, SP, PATO)
214 @namespaces = (
215 'GO',
216 'PO',
217 'SP',
218 'SO',
219 'PATO',
220 'CO',
223 else {
224 @namespaces = split /\s+/, $namespace; #split on whitespace
226 my @roots = ();
227 my $is_rel = 0;
228 foreach my $ns (@namespaces) {
229 $is_rel = 1 if $ns eq 'OBO_REL';
230 my $q = "SELECT cvterm.cvterm_id FROM cvterm
231 JOIN dbxref USING(dbxref_id) JOIN db USING(db_id)
232 LEFT JOIN cvterm_relationship ON (cvterm.cvterm_id=cvterm_relationship.subject_id)
233 WHERE cvterm_relationship.subject_id IS NULL AND is_obsolete= ? AND is_relationshiptype = ? AND db.name= ? ";
234 my $sth = $schema->storage->dbh->prepare($q);
235 $sth->execute(0,$is_rel,$ns);
236 while (my ($cvterm_id) = $sth->fetchrow_array() ) {
237 my $root = $schema->resultset("Cv::Cvterm")->find( { cvterm_id => $cvterm_id } );
238 push @roots, $root;
241 my @response_list = ();
243 foreach my $r (@roots) {
244 my $hashref = $self->flatten_node($r, undef);
245 push @response_list, $hashref;
247 $c->stash->{rest}= \@response_list;
251 =head2 match
253 Public Path: /<ns>/match
255 L<Catalyst::Action::REST> action.
257 Query/Body Params:
259 db_name
260 term_name
262 =cut
264 sub match : Local : ActionClass('REST') { }
266 sub match_GET {
267 my $self = shift;
268 my $c = shift;
269 my $db_name = $c->request->param("db_name");
270 my $term_name = $c->request->param("term_name");
272 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
273 my $query = "SELECT distinct cvterm.cvterm_id as cvterm_id , cv.name as cv_name , cvterm.name as cvterm_name , db.name || ':' || dbxref.accession as accession
274 FROM db
275 JOIN dbxref USING (db_id ) JOIN cvterm USING (dbxref_id)
276 JOIN cv USING (cv_id )
277 LEFT JOIN cvtermsynonym USING (cvterm_id )
278 WHERE db.name = ? AND (cvterm.name ilike ? OR cvtermsynonym.synonym ilike ? OR cvterm.definition ilike ?) AND cvterm.is_obsolete = 0
279 GROUP BY cvterm.cvterm_id,cv.name, cvterm.name, dbxref.accession, db.name ";
280 my $sth= $schema->storage->dbh->prepare($query);
281 $sth->execute($db_name, "\%$term_name\%", "\%$term_name\%", "\%$term_name\%");
282 my @response_list;
283 while (my $hashref = $sth->fetchrow_hashref ) {
284 push @response_list, $hashref;
286 $c->stash->{rest} = \@response_list;
289 =head2 cache
291 Public Path: /<ns>/cache
293 L<Catalyst::Action::REST> action.
295 Provides a list of parents and their direct children in a denormalized
296 list. The parameter node is used to determine all the children that
297 need to be cached for the parentage view to render fast (without
298 having to call the children ajax function).
300 =cut
302 sub cache : Local : ActionClass('REST') { }
304 sub cache_GET {
305 my $self =shift;
306 my $c = shift;
308 $self->{duplicates} = {};
309 $self->{cache_list} = [];
311 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
312 my %response;
313 my ($db_name, $accession) = split ":", $c->request->param('node');
314 if (!$db_name || !$accession) {
315 $response{error} = "Looks like you passed an illegal ontology term ID ! ($db_name : $accession) Please try again.";
316 $c->stash->{rest} = \%response;
317 return;
320 my $db = $schema->resultset('General::Db')->search({ name => $db_name })->first();
321 my $dbxref = $db->find_related('dbxrefs', { accession => $accession });
322 if (!$dbxref) {
323 $response{error} = "Did not find ontology term $db_name : $accession in the database. Please try again. If you think this term should exist please contact sgn-feedback\@sgn.cornell.edu";
324 $c->stash->{rest} = \%response;
325 return;
328 my $cvterm = $dbxref->cvterm;
329 if (!$cvterm) {
330 $response{error} = "Did not find ontology term $db_name : $accession in the database. This may be an internal database issue. Please contact sgn-feedback\@sgn.cornell.edu and we will fic this error ASAP";
331 $c->stash->{rest} = \%response;
332 return;
334 my $parents_rs = $cvterm->recursive_parents(); # returns a result set
335 if (!$parents_rs->next) {
336 $response{error} = "did not find recursive parents for cvterm " . $cvterm->name;
337 $c->stash->{rest} = \%response;
338 return;
341 # $self->add_cache_list(undef, $cvterm, $cvterm->type());
342 while (my $p = $parents_rs->next()) {
343 my $children_rs = $p->children();
344 while (my $rel_rs = $children_rs->next()) { # returns a list of cvterm rows
345 my $child = $rel_rs->subject();
346 $self->add_cache_list($p, $child, $rel_rs->type());
349 $c->stash->{rest} = $self->{cache_list};
353 =head1 PRIVATE ACTIONS
355 =head2 add_cache_list
357 Private action.
359 Adds an entry to the cache list.
361 Argsuments: 3 cvterm row objects: Parent, child, relationship
363 =cut
365 sub add_cache_list :Private {
366 my $self = shift;
367 my $parent = shift; # object
368 my $child = shift; # object
369 my $relationship = shift; #object
371 my $unique_hashkey = $parent->cvterm_id()." ".$child->cvterm_id();
372 if (exists($self->{duplicates}->{$unique_hashkey})) {
373 return;
376 $self->{duplicates}->{$unique_hashkey}++;
378 my $hashref = $self->flatten_node($child, $relationship);
379 ###$hashref->{parent} = $parent->get_full_accession();
381 my $dbxref = $parent->dbxref();
382 my $parent_accession = $dbxref->db()->name().":".$dbxref->accession();
383 $hashref->{parent} = $parent_accession;
384 ##print STDERR "Adding to cache list: parent=$parent. child=$child\n";
385 push @{$self->{cache_list}}, $hashref;
389 ### used for cvterm resultset
390 sub flatten_node {
391 my $self = shift;
392 my $node_row = shift;
393 my $rel_row = shift;
395 my $has_children = 0;
396 if ($node_row->children()->first()) {
397 $has_children = 1;
400 my $rel_name = "";
401 if ($rel_row) {
402 $rel_name = $rel_row->name();
405 my $dbxref = $node_row->dbxref();
407 my $hashref =
408 { accession => $dbxref->db->name().":".$dbxref->accession,
409 cvterm_name => $node_row->name(),
410 cvterm_id => $node_row->cvterm_id(),
411 has_children => $has_children,
412 relationship => $rel_name,