Browse Source

Added functions to simplify specifying WS Resrouce Class definitions

Stephen Ficklin 7 years ago
parent
commit
ce63300950

+ 0 - 1
tripal/tripal.module

@@ -406,7 +406,6 @@ function tripal_accesss_user_collections($uid) {
 function tripal_access_user_data($uid) {
   global $user;
 
-  drupal_debug($uid);
   if ($uid == $user->uid) {
     return TRUE;
   }

+ 225 - 2
tripal_ws/includes/TripalWebService.inc

@@ -54,6 +54,13 @@ class TripalWebService {
    */
   protected $base_path;
 
+  /**
+   * The list of classes used by this service. For the web service
+   * to be discoverable all of the entity classes and their collections
+   * must be defined.
+   */
+  protected $supported_classes;
+
   // --------------------------------------------------------------------------
   //                             CONSTRUCTORS
   // --------------------------------------------------------------------------
@@ -72,6 +79,15 @@ class TripalWebService {
     $this->path = array();
     $this->params = array();
     $this->base_path = $base_path;
+    $this->supported_classes = array();
+
+    $this->addClass(array(
+      "id" => "http://www.w3.org/ns/hydra/core#Resource",
+      "name" => 'resource',
+      "title" => "Resource",
+      'subClassOf' => NULL,
+    ));
+
   }
 
   // --------------------------------------------------------------------------
@@ -264,8 +280,215 @@ class TripalWebService {
    *   documentation for documenting supported classes.
    */
   public function getSupportedClasses() {
-     $supported_classes = array();
-     return $supported_classes;
+    return $this->supported_classes;
+  }
+
+  /**
+   *  Defines an entity class for the web services.
+   *
+   *  A class defines a resource including information about its properties
+   *  and the actions that can be performed.  Each class as a unique @id,
+   *  title, type and description.  The $details parameter is used to provide
+   *  this information.  Additionally, a resource may support Create, Read
+   *  Update and Delete (CRUD) operations.  Those are defined using the
+   *  $ops argument. Finally, resources may have properties (or fields). These
+   *  properties should be defined using the $props arugment.
+   *
+   *  @param $details.
+   *    An array of key/value pairs providing the details for the class. Valid
+   *    keys include:
+   *      - id: The unique IRI for this class.
+   *      - name: a computer-readable name for this class (i.e. no spaces,
+   *        no special characters).  This name is used to construct
+   *        type identifiers for operations.
+   *      - title:  The title for the resource that this Class represents.
+   *      - description: (Optional). A description of the resource.
+   *      - subClassOf: (Optional). If this class is a subclass of another
+   *        class then the value should be the @id of the parent class. This
+   *        defaults to hydra:Resource. If no subClass is desired, set it
+   *        to NULL.
+   *      - type: (Optional). Indicates the type of class. Defaults to
+   *        hydra:Class
+   *  @param $ops
+   *    If the resource supports CRUD functionality then those functions should
+   *    be defined using this argument.  This is an associative array with
+   *    the following keys: GET, POST, PUT, DELETE. These keys, if provided,
+   *    indicate that a resource of this type can be retrieved (GET),
+   *    created (POST), updated (PUT) or deleted (DELETE).  The value for each
+   *    key should be an associative array that supports the following keys:
+   *      = type: the @id that determines the type of operation.  This type
+   *        should be specific to the resource, and it need not be a term
+   *        from a real vocabulary.  Use the '_:' prefix for the term. For
+   *        example, for an 'Event' resource with a GET method, an appropriate
+   *        type would be '_:event_retrieve'.
+   *      - label: A label that describes the operation in the
+   *        context of the resource.
+   *      - description: A description for the operation.  Can be set to NULL
+   *        for no description.
+   *      - expects: The information expected by the Web API.
+   *      - returns: The @id of a Class that will be returned by
+   *        the operation. Set to NULL if there is no return value.
+   *      - statusCodes: An array of status codes that could be returned when
+   *        an error occurs.  Each element of the array is an associative
+   *        array with two key/value pairs: 'code' and 'description'.  Set the
+   *        'code' to be the HTTP code that can be returned on error and the
+   *        'description' to an error message appropriate for the error. For
+   *        example an HTTP 404 error means "Not Found".  You can sepecify this
+   *        code and provide a description appropriate for the method and
+   *        class.
+   *  @param $props.
+   *    Defines the list of properties that the resource provides.  This is an
+   *    array of properties where each property is an associative array
+   *    containing the following keys:
+   *      - type:  The @id of the type of property. Alternatively, this can
+   *        be an instance of a TripalWebServiceResource when the property
+   *        must support operations such as a property of type hydra:Link.
+   *      - title:  (Optional). The human-readable title of this property. If
+   *        this key is absent then the title will be set to the term's title.
+   *      - description:  (Optional). A short description of this property. If
+   *        this key is absent then the description will be set to the term's
+   *        description.
+   *      - required:  Set to TRUE to indicate if this property is a required
+   *        field when creating a new resource.
+   *      - readable:  Set to TRUE to indicate that the client can retrieve the
+   *        property's value?
+   *        altered by an update.
+   *      - writeable: Set to TRUE if the client can alter the value of the
+   *        property.
+   *      - domain: the @id of the rdfs:domain.
+   *      - range:  the @id of the rdfs:range.
+   */
+  protected function addClass($details = array(), $ops = array(), $props = array()) {
+    $supported_class = new TripalWebServiceResource($this->getServicePath());
+
+    // Add the context which all classes will need
+    $supported_class->addContextItem('description', 'rdfs:comment');
+
+    // Add in the Class details.
+    $class_id = $details['id'];
+    $supported_class->setID($class_id);
+    if (array_key_exists('type', $details)) {
+      $supported_class->setType($details['type']);
+    }
+    else {
+      $supported_class->setType('hydra:Class');
+    }
+    $supported_class->addProperty('hydra:title', $details['title']);
+    $supported_class->addProperty('hydra:description', array_key_exists('description', $details) ? $details['description'] : NULL);
+    if (array_key_exists('subClassOf', $details)) {
+      if ($details['subClassOf']) {
+        $supported_class->addProperty('hydra:subClassOf', $details['subClassOf']);
+      }
+    }
+    else {
+      $supported_class->addProperty('hydra:subClassOf', 'hydra:Resource');
+    }
+
+    // Now add the supported operations.
+    $class_ops = array();
+    foreach ($ops as $op => $op_details) {
+      $class_ops[] = $this->generateClassOp($supported_class, $op, $op_details);
+    }
+    $supported_class->addContextItem('supportedOperation', 'hydra:supportedOperation');
+    $supported_class->addProperty('supportedOperation', $class_ops);
+
+    // Now add in the supported proprerties.
+    $class_props = array();
+    foreach ($props as $prop) {
+      $class_props[] = $this->generateClassProp($supported_class, $prop);
+    }
+    $supported_class->addContextItem('supportedProperty', 'hydra:supportedProperty');
+    $supported_class->addProperty('supportedProperty', $class_props);
+
+    $this->supported_classes[] = $supported_class;
+  }
+
+  /**
+   * A helper function for the addClass() function for generating a property.
+   */
+  private function generateClassProp($supported_class, $prop) {
+    $supported_class->addContextItem('property', array(
+      "@id" => "hydra:property",
+      "@type" => "@id"
+    ));
+    $supported_class->addContextItem('domain', array(
+      "@id" => "rdfs:domain",
+      "@type" => "@id"
+    ));
+    $supported_class->addContextItem('range', array(
+      "@id" => "rdfs:range",
+      "@type" => "@id"
+    ));
+    $supported_class->addContextItem('readable', 'hydra:readable');
+    $supported_class->addContextItem('writeable', 'hydra:writeable');
+    $supported_class->addContextItem('required', 'hydra:required');
+    $class_prop = array(
+      'property' => $prop['type'],
+      'hydra:title' => $prop['title'],
+      'hydra:description' => array_key_exists('description', $prop) ? $prop['description'] : NULL,
+      'required' => array_key_exists('required', $prop) ? $prop['required'] : NULL,
+      'readable' => array_key_exists('readonly', $prop) ? $prop['readonly'] : NULL,
+      'writeable' => array_key_exists('writeonly', $prop) ? $prop['writeonly'] : NULL,
+    );
+    if (array_key_exists('domain', $prop)) {
+      $class_prop['domain'] = $prop['domain'];
+    }
+    if (array_key_exists('range', $prop)) {
+      $class_prop['range'] = $prop['range'];
+    }
+    if (array_key_exists('operations', $prop)) {
+      $class_prop['supportedOperation'] = array();
+      foreach ($prop['operations'] as $op => $op_details) {
+        $class_prop['supportedOperation'][] = $this->generateOp($supported_class, $op, $op_details);
+      }
+    }
+    return $class_prop;
+  }
+  /**
+   * A helper function for the addClass() function for generating an operation.
+   */
+  private function generateClassOp($supported_class, $op, $op_details) {
+
+    if ($op != 'GET' and $op != 'PUT' and $op != 'DELETE' and $op != 'POST') {
+      throw new Exception(t('The method, @method, provided to the TripalWebService::addClass function is not an oppropriate operations.', array('@method' => $op)));
+    }
+
+    if (!array_key_exists('type', $op_details)) {
+      throw new Exception(t('Please provide a type in the operations array passed to the TripalWebService::addClass() function: @details', array('@details' => print_r($op_details, TRUE))));
+    }
+
+    $class_op = new TripalWebServiceResource($this->base_path);
+    $class_op->setID($op_details['type']);
+    $class_op->setType('hydra:Operation');
+    $class_op->addContextItem('method', 'hydra:method');
+    $class_op->addContextItem('label', 'rdfs:label');
+    $class_op->addContextItem('description', 'rdfs:comment');
+    $class_op->addContextItem('expects', array(
+      "@id" => "hydra:expects",
+      "@type" => "@id"
+    ));
+    $class_op->addContextItem('returns', array(
+      "@id" => "hydra:returns",
+      "@type" => "@id"
+    ));
+    $class_op->addContextItem('statusCodes', 'hydra:statusCodes');
+
+    $class_op->addProperty('method', $op);
+    $class_op->addProperty('label', array_key_exists('label', $op_details) ? $op_details['label'] : 'Retrieves an instance of this resource');
+    $class_op->addProperty('description', array_key_exists('description', $op_details) ? $op_details['description'] : NULL);
+    $status_codes = array_key_exists('statusCodes', $op_details) ? $op_details['statusCodes'] : array();
+    foreach ($status_codes as $code) {
+      if (array_key_exists('code', $code) and array_key_exists('description', $code)) {
+        $class_op->addProperty('statusCodes', $code);
+      }
+      else {
+        throw new Exception(t('The status code provided to TripalWebService::addClass function is improperly formatted @code.', array('@code' => print_r($code, TRUE))));
+      }
+    }
+    $class_op->addProperty('expects', array_key_exists('expects', $op_details) ? $op_details['expects'] : NULL);
+    $class_op->addProperty('returns', array_key_exists('returns', $op_details) ? $op_details['returns'] : ' "http://www.w3.org/2002/07/owl#Nothing",');
+
+    return $class_op;
   }
 
   /**

+ 101 - 160
tripal_ws/includes/TripalWebService/TripalEntityService_v0_1.inc

@@ -1,7 +1,5 @@
 <?php
 
-use GraphAware\Bolt\Tests\Integration\ExceptionDispatchTest;
-
 class TripalEntityService_v0_1 extends TripalWebService {
 
   /**
@@ -30,6 +28,10 @@ class TripalEntityService_v0_1 extends TripalWebService {
    */
   public function __construct($base_path) {
     parent::__construct($base_path);
+
+    // Add the classes that this resource supports.
+    $this->addBundleClasses();
+    $this->addContentCollectionClass();
   }
 
   /**
@@ -866,6 +868,9 @@ class TripalEntityService_v0_1 extends TripalWebService {
   private function doAllTypesList() {
     $service_path = $this->getServicePath();
     $this->resource = new TripalWebServiceCollection($service_path, $this->params);
+    $this->resource->addContextItem('vocab', 'http://localhost/web-services/vocab/v0.1/');
+    $this->resource->addContextItem('ContentCollection', 'http://localhost/web-services/vocab/v0.1#ContentCollection');
+    $this->resource->setType('ContentCollection');
 
     $label = tripal_get_term_details('rdfs', 'label');
     $this->addResourceProperty($this->resource, $label, 'Content Types');
@@ -913,15 +918,60 @@ class TripalEntityService_v0_1 extends TripalWebService {
   }
 
   /**
-   * @see TripalWebService::getSupportedClasses()
+   * Adds the content collection class.
    */
-  public function getSupportedClasses() {
+  private function addContentCollectionClass() {
 
-    global $user;
+    $service_path = $this->getServicePath();
+    $details = array(
+      'id' => $service_path,
+      'title' => 'Content Collection',
+    );
+    $vocab = tripal_get_vocabulary_details('hydra');
+    $url = preg_replace('/{accession}/', 'member', $vocab['urlprefix']);
+    $propeties = array();
+    $propeties[] = array(
+      'type' => $url,
+      'title' => 'member',
+      'description' => "The list of available content types.",
+      "required" => null,
+      "readonly" => FALSE,
+      "writeonly" => FALSE,
+    );
+    $url = preg_replace('/{accession}/', 'totalItems', $vocab['urlprefix']);
+    $propeties[] = array(
+      "type" => $url,
+      "title" => "totalItems",
+      "description" => "The total number of content types.",
+      "required" => null,
+      "readonly" => FALSE,
+      "writeonly" => FALSE
+    );
+    $url = preg_replace('/{accession}/', 'label', $vocab['urlprefix']);
+    $propeties[] = array(
+      "type" => $url,
+      "title" => "label",
+      "description" => "The type content.",
+      "required" => null,
+      "readonly" => FALSE,
+      "writeonly" => FALSE
+    );
+
+    $operations = array();
+    $operations['GET'] = array(
+      'label' => 'Retrieves a collection of content types available on this site.',
+      'expects' => NULL,
+      'returns' => $service_path,
+      'type' => '_:content_collection_retrieve'
+    );
+    $this->addClass($details,$operations, $propeties);
+  }
+  /**
+   * Adds classes for every content type.
+   */
+  private function addBundleClasses() {
 
-    // An array of TripalWebServiceResources containing a
-    // description of the supported classes.
-    $supported_classes = array();
+    global $user;
 
     // Get the list of published terms (these are the bundle IDs)
     $bundles = db_select('tripal_bundle', 'tb')
@@ -929,7 +979,7 @@ class TripalEntityService_v0_1 extends TripalWebService {
       ->orderBy('tb.label', 'ASC')
       ->execute();
 
-    // Iterate through the terms and add an entry in the collection.
+    // Iterate through the content types and add a class for each one.
     $i = 0;
     while ($bundle = $bundles->fetchObject()) {
       $entity =  entity_load('TripalTerm', array('id' => $bundle->term_id));
@@ -943,182 +993,73 @@ class TripalEntityService_v0_1 extends TripalWebService {
         $description = $term->definition;
       }
 
-      $supported = new TripalWebServiceResource($this->getServicePath());
-      $supported->addContextItem('supportedOperation', 'hydra:supportedOperation');
-      $supported->addContextItem('supportedProperty', 'hydra:supportedProperty');
-      $supported->addContextItem('subClassOf', array(
-        "@id" => "rdfs:subClassOf",
-        "@type" => "@id"
-      ));
-      $supported->setID($this->getServicePath() . '/' . urlencode($bundle->label));
-      $supported->setType('hydra:Class');
-      $supported->addProperty('hydra:title', $bundle->label);
-      $supported->addProperty('hydra:description', $description);
-      $supported->addProperty('subClassOf', 'hydra:Resource');
+      $class_id = $this->getServicePath() . '/' . urlencode($bundle->label);
+      $details = array(
+        'id' => $class_id,
+        'title' => $bundle->label,
+        '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');
+        $label = "Retrieves a " . $bundle->label . " entity.";
         if (preg_match('/^[aeiou]/i', $bundle->label)) {
-          $get_op->addProperty('label', "Retrieves an " . $bundle->label . " entity.");
+          $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', $this->getServicePath() . '/' . urlencode($bundle->label));
-        $get_op->addProperty('statusCodes', array());
-        $operations[] = $get_op;
+        $operations['GET'] = array(
+          'label' => $label,
+          'description' => NULL,
+          'returns' => $class_id,
+          'type' => '_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_retrieve',
+        );
       }
 
-      /* // If the user can create this content type.
+      // 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');
+        $label = "Creates a " . $bundle->label . " entity.";
         if (preg_match('/^[aeiou]/i', $bundle->label)) {
-          $create_op->addProperty('label', "Creates an " . $bundle->label . " entity.");
+          $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;
+        $operations['POST'] = array(
+          'label' => $label,
+          'description' => NULL,
+          'returns' => $class_id,
+          'type' => '_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_create',
+        );
       }
 
       // 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');
+        $label = "Update and replace a " . $bundle->label . " entity.";
         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.");
+          $label = "Update and replace an " . $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;
+        $operations['PUT'] = array(
+          'label' => $label,
+          'description' => NULL,
+          'returns' => $class_id,
+          'type' => '_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_update',
+        );
       }
 
       // 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');
+        $label =  "Deletes a " . $bundle->label . " entity.";
         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.");
+          $label =  "Deletes an " . $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);
-
-      // Add in the properties by examining the fields attached to the bundle.
+        $operations['DELETE'] = array(
+          'label' => $label,
+          'description' => NULL,
+          'returns' => $class_id,
+          'type' => '_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_delete',
+        );
+      }
       $properties = array();
-      $supported->addProperty('supportedProperty', $properties);
-      $supported_classes[] = $supported;
-    }
 
-    return $supported_classes;
+      $this->addClass($details, $operations, $properties);
+    } // end while ($bundle = $bundles->fetchObject()) { ...
   }
 }

+ 70 - 186
tripal_ws/includes/TripalWebService/TripalVocabService_v0_1.inc

@@ -46,13 +46,19 @@ class TripalVocabService_v0_1 extends TripalWebService {
    * @see TripalWebService::handleRequest()
    */
   public function handleRequest() {
-    $this->resource->addContextItem('vocab', $this->getServicePath() . '#');
 
+    // Create the vocabulary resource.
+    $this->resource->addContextItem('vocab', $this->getServicePath() . '#');
     $this->resource->addContextItem('apiDocumentation', 'hydra:apiDocumentation');
     $this->resource->addContextItem('supportedClass', 'hydra:supportedClass');
     $this->resource->setType('apiDocumentation');
     $this->resource->setID('vocab/' . $this->getVersion());
 
+    // Add the EntryPoint class.
+    $this->addEntryPointClass();
+    foreach ($this->supported_classes as $supported) {
+      $this->resource->addProperty('supportedClass', $supported);
+    }
 
     // Iterate through all of the web services and build their documentation
     foreach ($this->services as $service_class) {
@@ -62,93 +68,6 @@ class TripalVocabService_v0_1 extends TripalWebService {
         $this->resource->addProperty('supportedClass', $supported);
       }
     }
-
-    // Add the EntryPoint class.
-    $entry_point = $this->getEntryPointClass();
-    $this->resource->addProperty('supportedClass', $entry_point);
-
-    // Add in the Collection class.
-    $collection = $this->getCollectionClass();
-    $this->resource->addProperty('supportedClass', $collection);
-  }
-
-  /**
-   * Generates the Collection class for the API documents.
-   *
-   * @return TripalWebServiceResource
-   *
-   */
-  private function getCollectionClass() {
-    // Add the Collection class.
-    $collection = new TripalWebServiceResource($this->getServicePath());
-    $collection->addContextItem('supportedOperation', 'hydra:supportedOperation');
-    $collection->addContextItem('supportedProperty', 'hydra:supportedProperty');
-    $collection->addContextItem('property', array(
-      "@id" => "hydra:property",
-      "@type" => "@id"
-    ));
-    $collection->setID('vocab:ContentCollection');
-    $collection->setType('hydra:Class');
-    $collection->addProperty('hydra:title', 'Content Collection');
-    $collection->addProperty('hydra:description', '');
-
-    $vocab = tripal_get_vocabulary_details('hydra');
-    $url = preg_replace('/{accession}/', 'member', $vocab['urlprefix']);
-    $collection->addProperty('supportedProperty', array(
-      "property" => $url,
-      "hydra:title" => "member",
-      "hydra:description" => "The members of this collection.",
-      "required" => null,
-      "readonly" => FALSE,
-      "writeonly" => FALSE,
-    ));
-
-    $url = preg_replace('/{accession}/', 'totalItems', $vocab['urlprefix']);
-    $collection->addProperty('supportedProperty', array(
-      "property" => $url,
-      "hydra:title" => "totalItems",
-      "hydra:description" => "The total number of items in the collection.",
-      "required" => null,
-      "readonly" => FALSE,
-      "writeonly" => FALSE
-    ));
-    $url = preg_replace('/{accession}/', 'label', $vocab['urlprefix']);
-    $collection->addProperty('supportedProperty', array(
-      "property" => $url,
-      "hydra:title" => "label",
-      "hydra:description" => "The type of entities within the collection.",
-      "required" => null,
-      "readonly" => FALSE,
-      "writeonly" => FALSE
-    ));
-
-    $collection_ops = array();
-    $collection_op_get = new TripalWebServiceResource($this->base_path);
-    $collection_op_get->setID('_:content_collection_retrieve');
-    $collection_op_get->setType('hydra:Operation');
-    $collection_op_get->addContextItem('method', 'hydra:method');
-    $collection_op_get->addContextItem('label', 'rdfs:label');
-    $collection_op_get->addContextItem('description', 'rdfs:comment');
-    $collection_op_get->addContextItem('expects', array(
-      "@id" => "hydra:expects",
-      "@type" => "@id"
-    ));
-    $collection_op_get->addContextItem('returns', array(
-      "@id" => "hydra:returns",
-      "@type" => "@id"
-    ));
-    $collection_op_get->addContextItem('statusCodes', 'hydra:statusCodes');
-    $collection_op_get->addProperty('statusCodes', array());
-    $collection_op_get->addProperty('method', 'GET');
-    $collection_op_get->addProperty('label', 'Retrieves members of the collection.');
-    $collection_op_get->addProperty('description', null);
-    $collection_op_get->addProperty('expects', null);
-    $collection_op_get->addProperty('returns', 'vocab:ContentCollection');
-    $collection_op_gets[] = $collection_op_get;
-
-    $collection->addProperty('supportedOperation', $collection_op_gets);
-
-    return $collection;
   }
 
   /**
@@ -157,123 +76,88 @@ class TripalVocabService_v0_1 extends TripalWebService {
    * @return TripalWebServiceResource
    *
    */
-  private function getEntryPointClass(){
-    // We need to list the content types as properties of the EntryPoint.
-    $entry_properties = array();
-
-    // Add the EntryPoint Class.
-    $entry_point = new TripalWebServiceResource($this->base_path);
-    $entry_point->setID('vocab:EntryPoint');
-    $entry_point->setType('hydra:Class');
-    $entry_point->addContextItem('supportedOperation', 'hydra:supportedOperation');
-    $entry_point->addContextItem('supportedProperty', 'hydra:supportedProperty');
-    $entry_point->addContextItem('label', 'rdfs:label');
-    $entry_point->addContextItem('description', 'rdfs:comment');
-    $entry_point->addContextItem('subClassOf', array(
-      "@id" => "rdfs:subClassOf",
-      "@type" => "@id"
-    ));
-    $entry_point->addProperty('label', 'EntryPoint');
-    $entry_point->addProperty('description', 'The main entry point or homepage of the API');
-    $entry_point->addProperty('subClassOf', NULL);
-
+  private function addEntryPointClass(){
+
+    $service_path = $this->getServicePath();
+    $details = array(
+      'id' => $service_path . '#EntryPoint',
+      'title' => 'EntryPoint',
+      'description' => 'The main entry point or homepage of the API',
+      'subClassOf' => NULL,
+    );
+
+    // Add each service as a property.
+    $properties = array();
     foreach ($this->services as $service_class) {
       $service = new $service_class($this->base_path);
 
-      $service_prop_prop = new TripalWebServiceResource($this->getServicePath());
-      $service_prop_prop->setID('vocab:EntryPoint/' . $service::$type);
-      $service_prop_prop->setType('hydra:Link');
-      $service_prop_prop->addContextItem('label', 'rdfs:label');
-      $service_prop_prop->addContextItem('description', 'rdfs:comment');
-      $service_prop_prop->addContextItem('domain', array(
+      // Create a WebServiceResource for the hydra:Link type.
+      $link = new TripalWebServiceResource($this->base_path);
+      $link->setID('vocab:EntryPoint/' . $service::$type);
+      $link->setType('hydra:Link');
+      $link->addContextItem('domain', array(
         "@id" => "rdfs:domain",
         "@type" => "@id"
       ));
-      $service_prop_prop->addContextItem('range', array(
+      $link->addContextItem('range', array(
         "@id" => "rdfs:range",
         "@type" => "@id"
       ));
-      $service_prop_prop->addContextItem('supportedOperation', 'hydra:supportedOperation');
-      $service_prop_prop->addContextItem('property', array(
-        "@id" => "hydra:property",
-        "@type" => "@id"
-      ));
-      $service_prop_prop->addProperty('label', $service_class::$label);
-      $service_prop_prop->addProperty('description', $service_class::$description);
-      $service_prop_prop->addProperty('domain', 'vocab:EntryPoint');
-      $service_prop_prop->addProperty('range', 'hydra:Collection');
-
-      // Add the GET operation to the property.
-      // TODO: this should be part of the class.
-      $service_prop_prop_ops = array();
-      $service_prop_prop_op_get = new TripalWebServiceResource($this->getServicePath());
-      $service_prop_prop_op_get->setID('_:collection_retrieve');
-      $service_prop_prop_op_get->setType('hydra:Operation');
-      $service_prop_prop_op_get->addContextItem('method', 'hydra:method');
-      $service_prop_prop_op_get->addContextItem('label', 'rdfs:label');
-      $service_prop_prop_op_get->addContextItem('description', 'rdfs:comment');
-      $service_prop_prop_op_get->addContextItem('expects', array(
+      $link->addContextItem('readable', 'hydra:readable');
+      $link->addContextItem('writeable', 'hydra:writeable');
+      $link->addContextItem('required', 'hydra:required');
+      $link->addProperty('hydra:title', $service_class::$label);
+      $link->addProperty('hydra:description', $service_class::$description);
+//       $link->addProperty('domain', $service_path . '#EntryPoint');
+//       $link->addProperty('range', $service_class::$label);
+
+      $ops = array();
+      $op = new TripalWebServiceResource($this->base_path);
+
+      $op->setID('_:' . $service::$type . '_retrieve');
+      $op->setType('hydra:Operation');
+      $op->addContextItem('method', 'hydra:method');
+      $op->addContextItem('label', 'rdfs:label');
+      $op->addContextItem('description', 'rdfs:comment');
+      $op->addContextItem('expects', array(
         "@id" => "hydra:expects",
         "@type" => "@id"
       ));
-      $service_prop_prop_op_get->addContextItem('returns', array(
+      $op->addContextItem('returns', array(
         "@id" => "hydra:returns",
         "@type" => "@id"
       ));
-      $service_prop_prop_op_get->addContextItem('statusCodes', 'hydra:statusCodes');
-      $service_prop_prop_op_get->addProperty('label', 'Retrieves all ' . $service_class::$label . ' entities.');
-      $service_prop_prop_op_get->addProperty('description', NULL);
-      $service_prop_prop_op_get->addProperty('method', 'GET');
-      $service_prop_prop_op_get->addProperty('expects', null);
-      $service_prop_prop_op_get->addProperty('returns', 'vocab:ContentCollection');
-      $service_prop_prop_op_get->addProperty('statusCodes', array());
-      $service_prop_prop_ops[] = $service_prop_prop_op_get;
-
-      // Now add the supported operations to this property.
-      $service_prop_prop->addProperty('supportedOperation', $service_prop_prop_ops);
-
-      // Finally, create the property for the EntryPoint that corresponds
-      // to this service type.
-      $service_prop = array(
-        'property' => $service_prop_prop,
-        'hydra:title' => $service_class::$type,
-        'hydra:description' => $service_class::$description,
-        'required' => null,
-        'readonly' => TRUE,
-        'writeonly' => FALSE,
+      $op->addContextItem('statusCodes', 'hydra:statusCodes');
+      $op->addProperty('method', "GET");
+      $op->addProperty('label', 'Retrieves ' . $service_class::$label . ' resources.');
+      $op->addProperty('description', NULL);
+      $op->addProperty('expects', NULL);
+      $op->addProperty('returns', $service->getServicePath());
+      $op->addProperty('statusCodes', array());
+      $ops[] = $op;
+      $link->addContextItem('supportedOperation', 'hydra:supportedOperation');
+      $link->addProperty('supportedOperation', $ops);
+
+      $property = array(
+        'type' => $link,
+        'title' => $service_class::$label,
+        'description' => $service_class::$description,
+        'domain', 'vocab:EntryPoint',
+        'range', $service->getServicePath(),
       );
-      $entry_properties[] = $service_prop;
+      $properties[] = $property;
     }
 
-    $entry_point->addProperty('supportedProperty', $entry_properties);
-
-
-    $service_ops = array();
-    $service_op_get = new TripalWebServiceResource($this->base_path);
-    $service_op_get->setID('_:entry_point');
-    $service_op_get->setType('hydra:Operation');
-    $service_op_get->addContextItem('method', 'hydra:method');
-    $service_op_get->addContextItem('label', 'rdfs:label');
-    $service_op_get->addContextItem('description', 'rdfs:comment');
-    $service_op_get->addContextItem('expects', array(
-      "@id" => "hydra:expects",
-      "@type" => "@id"
-    ));
-    $service_op_get->addContextItem('returns', array(
-      "@id" => "hydra:returns",
-      "@type" => "@id"
-    ));
-    $service_op_get->addContextItem('statusCodes', 'hydra:statusCodes');
-    $service_op_get->addProperty('statusCodes', array());
-    $service_op_get->addProperty('method', 'GET');
-    $service_op_get->addProperty('label', 'The APIs main entry point.');
-    $service_op_get->addProperty('description', null);
-    $service_op_get->addProperty('expects', null);
-    $service_op_get->addProperty('returns', 'vocab:EntryPoint');
-    $service_ops[] = $service_op_get;
+    $operations = array();
+    $operations['GET'] = array(
+      'label' => "The APIs main entry point.",
+      'description' => NULL,
+      'expects' => NULL,
+      'returns' => $service_path . '#EntryPoint',
+      'type' => '_:entry_point_retrieve'
+    );
 
-    $entry_point->addProperty('supportedOperation', $service_ops);
 
-    return $entry_point;
+    $this->addClass($details, $operations, $properties);
   }
 }

+ 3 - 1
tripal_ws/includes/TripalWebServiceCollection.inc

@@ -102,7 +102,9 @@ class TripalWebServiceCollection extends TripalWebServiceResource {
    * @see TripalWebServiceResource::setType()
    */
   public function setType($type) {
-    throw new Exception("The type for a Collection can only be collection.");
+    // TODO: There should be a check to make sure that the type is a
+    // subclass of the hydra:Collection term.
+    parent::setType($type);
   }
 
   /**

+ 17 - 6
tripal_ws/includes/TripalWebServiceResource.inc

@@ -163,15 +163,15 @@ class TripalWebServiceResource {
 
       // 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.");
+        throw new Exception(t("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.", array('!key' => $key)));
       }
     }
     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.");
+        throw new Exception(t("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.", array('!key' => $key)));
       }
     }
   }
@@ -188,8 +188,19 @@ class TripalWebServiceResource {
    */
   private function checkValue(&$value) {
     if (is_array($value)) {
-      foreach ($value as $i => $v) {
-        $this->checkValue($value[$i]);
+      foreach ($value as $k => $v) {
+        // If this is an integer then this is a numeric indexed array
+        // and we can just check the value.  If not, then make sure the
+        // key has been added to the context.
+        if (preg_match('/^\d+$/', $k)) {
+          $this->checkValue($value[$k]);
+        }
+        else {
+          if ($k != '@id' and $k != '@type') {
+            $this->checkKey($k);
+          }
+          $this->checkValue($value[$k]);
+        }
       }
     }
     else {