Browse Source

web services API is more fleshed out. needs supported properites still

Stephen Ficklin 7 years ago
parent
commit
5133ed82fa

+ 1 - 1
tripal_chado/includes/TripalFields/sio__annotation/sio__annotation.inc

@@ -135,7 +135,7 @@ class sio__annotation extends ChadoField {
         'name' => 'rank',
         'label' => 'Annotation Term Rank',
         'operations' => array('eq', 'ne', 'lte', 'gte'),
-        'type' => numeric
+        'type' => 'numeric'
       );
     }
     if (array_key_exists('pub_id', $schema['fields'])) {

+ 16 - 5
tripal_ws/includes/TripalWebService.inc

@@ -62,7 +62,7 @@ class TripalWebService {
    */
   public function __construct($base_path) {
     if (!$base_path) {
-      throw new Exception('Pleaes provide a $base_path argument when creating a new TripalWebService.');
+      throw new Exception('Please provide a $base_path argument when creating a new TripalWebService.');
     }
 
     // Create a default resource so that the service always some something.
@@ -190,11 +190,7 @@ class TripalWebService {
       '@id' => '',
       '@type' => $type,
     );
-
-    // Get the data array and set the IRIs fore each ID.
     $data = $this->getData();
-    //$this->setIDs($data);
-
     return array_merge($json_ld, $data);
   }
 
@@ -255,4 +251,19 @@ class TripalWebService {
     $this->resource->addContextItem('error', 'rdfs:error');
     $this->resource->addProperty('error', $message);
   }
+
+  /**
+   * Retrieves an array contining the supported classes.
+   *
+   * Supported classe are resources provided by this web services and the
+   * operations supported by those classes.
+   *
+   * @return
+   *   An array of TripalWebServiceResource objects that follow the Hydra
+   *   documentation for documenting supported classes.
+   */
+  public function getSupportedClasses() {
+     $supported_classes = array();
+     return $supported_classes;
+  }
 }

+ 0 - 19
tripal_ws/includes/TripalWebService/TripalDocService_V0_1.inc

@@ -1,19 +0,0 @@
-<?php
-
-class TripalDocService_v0_1 extends TripalWebService {
-
-  /**
-   * The human-readable label for this web service.
-   */
-  public static $label = 'API Documentation';
-  /**
-   * A bit of text to describe what this service provides.
-   */
-  public static $description = 'Provides documentation for the use of this web services.';
-  /**
-   * A machine-readable type for this service. This name must be unique
-   * among all Tripal web services and is used to form the URL to access
-   * this service.
-   */
-  public static $type = 'doc';
-}

+ 205 - 0
tripal_ws/includes/TripalWebService/TripalEntityService_v0_1.inc

@@ -713,4 +713,209 @@ class TripalEntityService_v0_1 extends TripalWebService {
 
     }
   }
+
+  /**
+   * @see TripalWebService::getSupportedClasses()
+   */
+  public function getSupportedClasses() {
+
+    global $user;
+
+    // An array of TripalWebServiceResources containing a
+    // description of the supported classes.
+    $supported_classes = array();
+
+    // Get the list of published terms (these are the bundle IDs)
+    $bundles = db_select('tripal_bundle', 'tb')
+      ->fields('tb')
+      ->orderBy('tb.label', 'ASC')
+      ->execute();
+
+    // Iterate through the terms and add an entry in the collection.
+    $i = 0;
+    while ($bundle = $bundles->fetchObject()) {
+      $entity =  entity_load('TripalTerm', array('id' => $bundle->term_id));
+      $term = reset($entity);
+      $vocab = $term->vocab;
+
+      // Get the bundle description. If no description is provided then
+      // use the term definition
+      $description = tripal_get_bundle_variable('description', $bundle->id);
+      if (!$description) {
+        $description = $term->definition;
+      }
+
+      $supported = new TripalWebServiceResource($this->getServicePath());
+      $supported->addContextItem('supportedOperation', 'hydra:supportedOperation');
+      $supported->addContextItem('supportedProperty', 'hydra:supportedProperty');
+      $supported->setID(urlencode($bundle->label));
+      $supported->setType('hydra:Class');
+      $supported->addProperty('hydra:title', $bundle->label);
+      $supported->addProperty('hydra:description', $description);
+
+      // Add in the supported operations for this content type.
+      $operations = array();
+
+      // If the user can view this content type.
+      if (user_access('view ' . $bundle->name)) {
+        // All content types allow GET (if the user has view permission).
+        $get_op = new TripalWebServiceResource($this->getServicePath());
+        $get_op->addContextItem('supportedOperation', 'hydra:supportedOperation');
+        $get_op->addContextItem('method', 'hydra:method');
+        $get_op->addContextItem('statusCodes', 'hydra:statusCodes');
+        $get_op->addContextItem('label', 'rdfs:label');
+        $get_op->addContextItem('description', 'rdfs:comment');
+        $get_op->addContextItem('expects', array(
+          "@id" => "hydra:expects",
+          "@type" => "@id"
+        ));
+        $get_op->addContextItem('returns', array(
+          "@id" => "hydra:returns",
+          "@type" => "@id"
+        ));
+        $get_op->setID('_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_retrieve');
+        $get_op->setType('hydra:Operation');
+        $get_op->addProperty('method', 'GET');
+        if (preg_match('/^[aeiou]/i', $bundle->label)) {
+          $get_op->addProperty('label', "Retrieves an " . $bundle->label . " entity.");
+        }
+        else {
+          $get_op->addProperty('label', "Retrieves a " . $bundle->label . " entity.");
+        }
+        $get_op->addProperty('description', NULL);
+        $get_op->addProperty('expects', NULL);
+        $get_op->addProperty('returns', $term->url);
+        $get_op->addProperty('statusCodes', array(
+          array(
+            'code' => 404,
+            'description' => 'The ' . $bundle->label . ' could not be found using the provided ID.'
+          ),
+        ));
+        $operations[] = $get_op;
+      }
+
+      // If the user can create this content type.
+      if (user_access('create ' . $bundle->name)) {
+        // All content types allow GET (if the user has view permission).
+        $create_op = new TripalWebServiceResource($this->getServicePath());
+        $create_op->addContextItem('method', 'hydra:method');
+        $create_op->addContextItem('statusCodes', 'hydra:statusCodes');
+        $create_op->addContextItem('label', 'rdfs:label');
+        $create_op->addContextItem('description', 'rdfs:comment');
+        $create_op->addContextItem('code', 'hydra:statusCode');
+        $create_op->addContextItem('expects', array(
+          "@id" => "hydra:expects",
+          "@type" => "@id"
+        ));
+        $create_op->addContextItem('returns', array(
+          "@id" => "hydra:returns",
+          "@type" => "@id"
+        ));
+        $create_op->setID('_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_create');
+        $create_op->setType('http://schema.org/CreateAction');
+        $create_op->addProperty('method', 'POST');
+        if (preg_match('/^[aeiou]/i', $bundle->label)) {
+          $create_op->addProperty('label', "Creates an " . $bundle->label . " entity.");
+        }
+        else {
+          $create_op->addProperty('label', "Creates a " . $bundle->label . " entity.");
+        }
+        $create_op->addProperty('description', NULL);
+        $create_op->addProperty('expects', $term->url);
+        $create_op->addProperty('returns', $term->url);
+        $create_op->addProperty('statusCodes', array(
+          array(
+            'code' => 404,
+            'description' => 'The ' . $bundle->label . ' could not be created.'
+          ),
+          array(
+            'code' => 409,
+            'description' => 'The ' . $bundle->label . ' already exists.'
+          ),
+        ));
+        $operations[] = $create_op;
+      }
+
+      // If the user can edit this content type.
+      if (user_access('edit ' . $bundle->name)) {
+        // All content types allow GET (if the user has view permission).
+        $edit_op = new TripalWebServiceResource($this->getServicePath());
+        $edit_op->addContextItem('method', 'hydra:method');
+        $edit_op->addContextItem('statusCodes', 'hydra:statusCodes');
+        $edit_op->addContextItem('label', 'rdfs:label');
+        $edit_op->addContextItem('description', 'rdfs:comment');
+        $edit_op->addContextItem('code', 'hydra:statusCode');
+        $edit_op->addContextItem('expects', array(
+          "@id" => "hydra:expects",
+          "@type" => "@id"
+        ));
+        $edit_op->addContextItem('returns', array(
+          "@id" => "hydra:returns",
+          "@type" => "@id"
+        ));
+        $edit_op->setID('_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_update');
+        $edit_op->setType('http://schema.org/UpdateAction');
+        $edit_op->addProperty('method', 'PUT');
+        if (preg_match('/^[aeiou]/i', $bundle->label)) {
+          $edit_op->addProperty('label', "Update and replace an " . $bundle->label . " entity.");
+        }
+        else {
+          $edit_op->addProperty('label', "Update and replace a " . $bundle->label . " entity.");
+        }
+        $edit_op->addProperty('description', NULL);
+        $edit_op->addProperty('expects', $term->url);
+        $edit_op->addProperty('returns', $term->url);
+        $edit_op->addProperty('statusCodes', array(
+          array(
+            'code' => 404,
+            'description' => 'The ' . $bundle->label . ' could not be updated using the provided ID.'
+          ),
+        ));
+        $operations[] = $edit_op;
+      }
+
+      // If the user can edit this content type.
+      if (user_access('delete ' . $bundle->name)) {
+        // All content types allow GET (if the user has view permission).
+        $delete_op = new TripalWebServiceResource($this->getServicePath());
+        $delete_op->addContextItem('method', 'hydra:method');
+        $delete_op->addContextItem('statusCodes', 'hydra:statusCodes');
+        $delete_op->addContextItem('label', 'rdfs:label');
+        $delete_op->addContextItem('description', 'rdfs:comment');
+        $delete_op->addContextItem('code', 'hydra:statusCode');
+        $delete_op->addContextItem('expects', array(
+          "@id" => "hydra:expects",
+          "@type" => "@id"
+        ));
+        $delete_op->addContextItem('returns', array(
+          "@id" => "hydra:returns",
+          "@type" => "@id"
+        ));
+        $delete_op->setID('_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_delete');
+        $delete_op->setType('http://schema.org/DeleteAction');
+        $delete_op->addProperty('method', 'DELETE');
+        if (preg_match('/^[aeiou]/i', $bundle->label)) {
+          $delete_op->addProperty('label', "Deletes an " . $bundle->label . " entity.");
+        }
+        else {
+          $delete_op->addProperty('label', "Deletes a " . $bundle->label . " entity.");
+        }
+        $delete_op->addProperty('description', NULL);
+        $delete_op->addProperty('expects', $term->url);
+        $delete_op->addProperty('returns', $term->url);
+        $delete_op->addProperty('statusCodes', array(
+          array(
+            'code' => 404,
+            'description' => 'The ' . $bundle->label . ' could not be deleted using the provided ID.'
+          ),
+        ));
+        $operations[] = $delete_op;
+      }
+
+      $supported->addProperty('supportedOperation', $operations);
+      $supported_classes[] = $supported;
+    }
+
+    return $supported_classes;
+  }
 }

+ 23 - 1
tripal_ws/includes/TripalWebService/TripalVocabService_v0_1.inc

@@ -15,5 +15,27 @@ class TripalVocabService_v0_1 extends TripalWebService {
    * among all Tripal web services and is used to form the URL to access
    * this service.
    */
-  public static $type = 'vocabulary';
+  public static $type = 'vocab';
+
+
+  /**
+   * @see TripalWebService::handleRequest()
+   */
+  public function handleRequest() {
+
+    $this->resource->addContextItem('ApiDocumentation', 'hydra:ApiDocumentation');
+    $this->resource->addContextItem('supportedClass', 'hydra:supportedClass');
+    $this->resource->setType('ApiDocumentation');
+
+    // Iterate through all of the web services and get their documentation
+    $services = tripal_get_web_services();
+    foreach ($services as $service_class) {
+      tripal_load_include_web_service_class($service_class);
+      $service = new $service_class($this->base_path);
+      $supported_classes = $service->getSupportedClasses();
+      foreach ($supported_classes as $supported) {
+        $this->resource->addProperty('supportedClass', $supported);
+      }
+    }
+  }
 }

+ 107 - 45
tripal_ws/includes/TripalWebServiceResource.inc

@@ -96,16 +96,59 @@ class TripalWebServiceResource {
    *   The type
    */
   public function setType($type) {
-    $keys = array_keys($this->context);
-    if (!in_array($type, $keys)) {
-      throw new Exception("The resource type, '$type', has not yet been added to the " .
-          "context of the web service. Use the addContextItem() function of the web service " .
-          "to add this term.");
-    }
+    $this->checkKey($type);
     $this->type = $type;
     $this->data['@type'] = $type;
   }
 
+  /**
+   * Checks a key to ensure it is in the Context before being used.
+   *
+   * This function should be used before adding a property or type to this
+   * resource.  It ensures that the key for the property is already in the
+   * context.
+   *
+   * @param $key
+   *   The key to check.
+   * @throws Exception
+   */
+  private function checkKey($key) {
+    // Make sure the key is already present in the context.
+    $keys = array_keys($this->context);
+
+    // Keys that are full HTML are acceptable
+    if (preg_match('/^(http|https):\/\/.*/', $key)) {
+      return;
+    }
+
+    // If the key has a colon separating the vocabulary and the term then we
+    // just need to make sure that the vocabulary is present.
+    $matches = array();
+    if (preg_match('/^(.*?):(.*?)$/', $key, $matches)) {
+      $vocab = $matches[1];
+      $accession = $matches[2];
+
+      // The underscore represents the blank node. So, these keys are okay.
+      if ($vocab == '_') {
+        return;
+      }
+
+      // If the vocabulary is not in the.
+      if (!in_array($vocab, $keys)) {
+        throw new Exception("The key, '$key', has a vocabulary that has not yet been added to the " .
+            "context. Use the addContextItem() function to add the vocabulary prior to adding a value for it.");
+      }
+    }
+    else {
+      // If the key is not in the context then throw an error.
+      if (!in_array($key, $keys)) {
+        throw new Exception("The key, '$key', has not yet been added to the " .
+            "context. Use the addContextItem() function to add this key prior to adding a value for it.");
+      }
+    }
+  }
+
+
   /**
    * Set's the unique identifier for the resource.
    *
@@ -119,7 +162,29 @@ class TripalWebServiceResource {
    */
   public function setID($id) {
     $this->id = $id;
-    $this->data['@id'] = $this->service_path . '/' . $id;
+    $this->data['@id'] = $this->getURI($id);
+  }
+
+  /**
+   * Retreives the IRI for an entity of a given ID in this web service.
+   *
+   * @param $id
+   *   The unique identifier for the resource.
+   */
+  public function getURI($id) {
+    // If this is a key/value pair for an id and if the vocab portion is
+    // an underscore then this represents a blank node and we don't want
+    // to add the full path.
+    $matches = array();
+    if (preg_match('/^(.*?):(.*?)$/', $id, $matches)) {
+      $vocab = $matches[1];
+      if ($vocab == '_') {
+        return $id;
+      }
+    }
+    else {
+      return $this->service_path . '/' . $id;
+    }
   }
 
 
@@ -152,7 +217,8 @@ class TripalWebServiceResource {
    * Adds a new key/value pair to the web serivces response.
    *
    * The value must either be a scalar or another TripalWebServiceResource
-   * object.
+   * object.  If the same key is usesd multiple times then the resulting
+   * resource will be presented as an array of elements.
    *
    * @param unknown $key
    *   The name of the $key to add. This key must already be present in the
@@ -164,49 +230,45 @@ class TripalWebServiceResource {
    */
   public function addProperty($key, $value) {
 
-    // Make sure the key is already present in the context.
-    $keys = array_keys($this->context);
-    if (!in_array($key, $keys)) {
-      throw new Exception("The key, '$key', has not yet been added to the " .
-          "context. Use the addContextItem() function to add this key prior to adding a value for it.");
+    $this->checkKey($key);
+
+    // If this is a numeric array then go through each element and add them.
+    if (is_array($value) and count(array_filter(array_keys($value), 'is_int')) == count(array_keys($value))) {
+      if (!array_key_exists($key, $this->data)) {
+        $this->data[$key] = array();
+      }
+      foreach ($value as $item) {
+        $this->addProperty($key, $item);
+      }
+      return;
     }
-    if (is_scalar($value)) {
-      $this->data[$key] = $value;
+
+    // If this is a TripalWebServiceResource then get it's data and use that
+    // as the new value.
+    if (is_a($value, 'TripalWebServiceResource') or is_subclass_of($value, 'TripalWebServiceResource')) {
+      // Add in the context items to this resource.
+      $context = $value->getContext();
+      foreach ($context as $k => $v) {
+        $this->addContextItem($k, $v);
+      }
+      $value = $value->getData();
     }
-    else if (!is_subclass_of($value, 'TripalWebServiceResource')) {
+
+    // If this key doesn't exist then add the value directly to the key.
+    if (!array_key_exists($key, $this->data)) {
       $this->data[$key] = $value;
     }
+    // Else if it does exist then we need to make sure that the element is
+    // an array and add it.
     else {
-      throw new Exception("The value must either be a scalar or a TripalWebServiceResource");
-    }
-  }
-
-  /**
-   * A recursive function that ensures all keys in an item are in the context.
-   *
-   * @param $key
-   *   The name of the current key.
-   * @param $value
-   *   The avlue assigned to the current key.
-   *
-   * @throws Exception
-   *   Throws an exception of a key is not present in the context.
-   */
-  private function checkDataItem($key, $value) {
-    // Make sure the key is already present in the context.
-    $keys = array_keys($this->context);
-    if (!in_array($key, $keys)) {
-      throw new Exception("The key, '$key', has not yet been added to the " .
-        "context. Use the addContextItem() function to add this key prior to adding a value for it.");
-    }
-    // If the value is an associative array then recurse
-    if (is_array($value)) {
-      // Check if this is an associatave array (non-integer keys).
-      if (count(array_filter(array_keys($array), 'is_string')) > 0) {
-        foreach ($value as $sub_key => $sub_value) {
-          $this->checkDataItem($sub_key, $sub_value);
-        }
+      // If this is the second element, then convert it to an array.  The
+      // second test in the condition below is for for a numeric array.
+      if (!is_array($this->data[$key]) or count(array_filter(array_keys($this->data[$key]), 'is_string')) > 0) {
+        $element = $this->data[$key];
+        $this->data[$key] = array();
+        $this->data[$key][] = $element;
       }
+      $this->data[$key][] = $value;
     }
   }