CohortExplorer

 view release on metacpan or  search on metacpan

lib/CohortExplorer/Application/Opal/Datasource.pm  view on Meta::CPAN

package CohortExplorer::Application::Opal::Datasource;

use strict;
use warnings;

our $VERSION = 0.14;

use base qw(CohortExplorer::Datasource);
use JSON qw( decode_json );

#-------
sub authenticate {
 my ( $self, %opts ) = @_;
 my $ds_name = $self->name;
 require LWP::UserAgent;
 require MIME::Base64;

 # Authenticate using Opal url
 # Default Opal url is http://localhost:8080
 my $ua = LWP::UserAgent->new( timeout => 10, ssl_opts => { verify_hostname => 0 } );
(my $url = $self->url || 'http://localhost:8080' ) =~ s/\/$//;
 my $req = HTTP::Request->new( GET => $url . "/ws/datasource/$ds_name" );
 $req->header(
             Authorization => "X-Opal-Auth "
               . MIME::Base64::encode( join ':', @opts{qw/username password/} ),
             Accept => "application/json"
 );
 my $res  = $ua->request($req);
 my $code = $res->code;
 if ( $code == 200 ) {
  my $json = decode_json( $res->decoded_content );
  if ( $json->{type} ne 'mongodb' ) {

   # Successful authentication returns tables and views accessible to the user
   my %view = map { $_ => 1 } @{ $json->{view} || [] };
   my @tables = @{ $json->{table} || [] };

   # Get all base tables ( i.e. exclude views )
   my @base_tables =
     defined $json->{view} ? grep { not $view{$_} } @tables : @tables;
   if ( @base_tables == 0 ) {
    die "No tables but views found in $ds_name\n";
   }
   return \@base_tables;
  }
  else {
   die "Storage type for $ds_name is not MySQL but MongoDB\n";
  }
 }
 elsif ( $code == 401 ) {
  return;
 }
 else {
  die "Failed to connect to Opal server using '$url' (error $code)\n";
 }
}

sub additional_params {
 my ( $self, $res, %opts ) = @_;
 my $ds_name = $self->name;

 # By default,
 # datasource type is standard (i.e. cross-sectional)
 # entity_type is participant
 # id_visit_separator is '_' (valid to longitudinal datasources only)
 my %param = (
               type        => $self->type        || 'standard',
               entity_type => $self->entity_type || 'Participant',
               allowed_tables => $res,
               username       => $opts{username}
 );

 if ( $param{type} eq 'longitudinal' ) {
     $param{id_visit_separator} = $self->id_visit_separator || '_';

  # Get static tables (if any) from datasource-config.properties and check them against @allowed_tables
  my @static_tables = ();
  my %table = map { $_ => 1 } @{ $param{allowed_tables} };
  $param{static_tables} = $self->static_tables || undef;
  if ( $param{static_tables} ) {
   for ( split /,\s*/, $param{static_tables} ) {
    push @static_tables, $_ if ( $table{$_} );
   }
  }
  $param{static_tables} = \@static_tables;
 }
 else {

  # id_visit_separator and visit_max is undefined for standard datasources
  @param{qw/id_visit_separator visit_max static_tables/} =
    ( undef, undef, $param{allowed_tables} );
 }

 # Get list of allowed variables from each table
 my $ua = LWP::UserAgent->new( timeout => 10, ssl_opts => { verify_hostname => 0 } );
(my $url = $self->url || 'http://localhost:8080' ) =~ s/\/$//;
 for my $t ( @{ $param{allowed_tables} } ) {
  my $entity_req =
    HTTP::Request->new(
                    GET => $url . "/ws/datasource/$ds_name/table/$t/entities" );
  $entity_req->header(
             Authorization => "X-Opal-Auth "
               . MIME::Base64::encode( join ':', @opts{qw/username password/} ),
             Accept => "application/json"
  );
  my $entity_res = $ua->request($entity_req);

  # Get the first identifier
  if ( $entity_res->code == 200 ) {
   my $decoded_json = decode_json( $entity_res->decoded_content );
   my $var_req =
     HTTP::Request->new(   GET => $url
                         . "/ws/datasource/$ds_name/table/$t/valueSet/"
                         . ( $decoded_json->[0]->{identifier} || '' ) );
   $var_req->header(
             Authorization => "X-Opal-Auth "
               . MIME::Base64::encode( join ':', @opts{qw/username password/} ),
             Accept => "application/json"
   );
   my $var_res = $ua->request($var_req);

   # Get all variables with accessible values
   if ( $var_res->code == 200 ) {
    $decoded_json = decode_json( $var_res->decoded_content );
    push @{ $param{allowed_variables} },
      map { "$t.$_" } @{ $decoded_json->{variables} };
   }
   else {
    die "Failed to fetch variable list via $url (error "
      . $var_res->code . ")\n";
   }
  }
  else {
   die "Failed to fetch variable list via $url (error "
     . $entity_res->code . ")\n";
  }
 }
 return \%param;
}

sub entity_structure {
 my ($self) = @_;
 my %struct = (
  -columns => [
                variable => 'var.name',
                value    => 'vsv.value',
                table    => 'vt.name'
  ],
  -from => [
   -join =>
     qw/variable_entity|ve id=variable_entity_id value_set|vs <=>{value_table_id=id} value_table|vt <=>{vs.id=value_set_id} value_set_value|vsv <=>{vsv.variable_id=id} variable|var <=>{vt.datasource_id=id} datasource|ds/
  ],
  -where => {
              've.type' => $self->entity_type,
              'ds.name' => $self->name
  }
 );

# For longitudinal datasources split identifier into entity_id and visit using id_split_separator
 if ( $self->type eq 'longitudinal' ) {
  my $id_visit_sep = $self->id_visit_separator;
  push @{ $struct{-columns} },
    entity_id => "SUBSTRING_INDEX( ve.identifier, '$id_visit_sep', 1)";
  my $visit = "CAST( SUBSTRING_INDEX( ve.identifier, '$id_visit_sep', IF( ve.identifier RLIKE '$id_visit_sep\[0-9\]+\$', -1, NULL ) ) AS UNSIGNED )";

  # Check for the presence of id_visit_sep
  push @{ $struct{-columns} }, visit => $visit;
  $struct{-order_by} = $visit;
 }
 else {
  push @{ $struct{-columns} }, entity_id => 've.identifier';
 }



( run in 0.669 second using v1.01-cache-2.11-cpan-5837b0d9d2c )