Browse Source

Updated web services to work with the HydraConsole

Stephen Ficklin 7 years ago
parent
commit
53937d9639

+ 25 - 0
tripal/api/tripal.terms.api.inc

@@ -105,6 +105,11 @@ function hook_vocab_import_form_submit($form, &$form_state) {
  *       -name:  The short name for the vocabulary (e.g. SO, PATO, etc).
  *       -description: The description of this vocabulary.
  *       -url: The URL for the vocabulary.
+ *       -urlprefix : (optional) A URL to which the short_name and term
+ *        accession can be appended to form a complete URL for a term.  If the
+ *        prefix does not support appending then the exact location for the
+ *        position of the short_name and the term accession will be
+ *        specified with the {db} and {accession} tags respectively.
  *     -accession : The name unique ID of the term.
  *     -url : The URL for the term.
  *     -name : The name of the term.
@@ -132,6 +137,11 @@ function hook_vocab_get_term($vocabulary, $accession) {
  *     - short_name : The short name abbreviation for the vocabulary.
  *     - description : A brief description of the vocabulary.
  *     - url : (optional) A URL for the online resources for the vocabulary.
+ *     - urlprefix : (optional) A URL to which the short_name and term
+ *       accession can be appended to form a complete URL for a term.  If the
+ *       prefix does not support appending then the exact location for the
+ *       position of the short_name and the term accession will be
+ *       specified with the {db} and {accession} tags respectively.
  */
 function hook_vocab_get_vocabulary($vocabulary) {
   // See the tripal_chado_vocab_get_vocabulary() function for an example.
@@ -145,6 +155,11 @@ function hook_vocab_get_vocabulary($vocabulary) {
  *       -name:  The short name for the vocabulary (e.g. SO, PATO, etc).
  *       -description: The description of this vocabulary.
  *       -url: The URL for the vocabulary.
+ *       -urlprefix: (optional) A URL to which the short_name and term
+ *         accession can be appended to form a complete URL for a term.  If the
+ *         prefix does not support appending then the exact location for the
+ *         position of the short_name and the term accession will be
+ *         specified with the {db} and {accession} tags respectively.
  *     -accession : The name unique ID of the term.
  *     -url : The URL for the term.
  *     -name : The name of the term.
@@ -210,6 +225,11 @@ function tripal_add_term($details) {
  *       - short_name : The short name abbreviation for the vocabulary.
  *       - description : A brief description of the vocabulary.
  *       - url : (optional) A URL for the online resources for the vocabulary.
+ *       - urlprefix : (optional) A URL to which the short_name and term
+ *         accession can be appended to form a complete URL for a term.  If the
+ *         prefix does not support appending then the exact location for the
+ *         position of the short_name and the term accession will be
+ *         specified with the {db} and {accession} tags respectively.
  *     - accession : The name unique ID of the term.
  *     - url : The URL for the term.
  *     - name : The name of the term.
@@ -254,6 +274,11 @@ function tripal_get_term_details($vocabulary, $accession) {
  *     - short_name : The short name abbreviation for the vocabulary.
  *     - description : A brief description of the vocabulary.
  *     - url : (optional) A URL for the online resources for the vocabulary.
+ *     - urlprefix : (optional) A URL to which the short_name and term
+ *       accession can be appended to form a complete URL for a term.  If the
+ *       prefix does not support appending then the exact location for the
+ *       position of the short_name and the term accession will be
+ *       specified with the {db} and {accession} tags respectively.
  */
 function tripal_get_vocabulary_details($vocabulary) {
   // TODO: we need some sort of administrative interface that lets the user

+ 23 - 3
tripal_chado/includes/tripal_chado.semweb.inc

@@ -65,11 +65,13 @@ function tripal_chado_populate_vocab_FOAF() {
  */
 function tripal_chado_populate_vocab_HYDRA() {
 
+  // For the HydraConsole to work with webservices the URL must be set as
+  // http://www.w3.org/ns/hydra/core
   tripal_insert_db(array(
     'name' => 'hydra',
     'description' => 'A Vocabulary for Hypermedia-Driven Web APIs',
-    'url' => 'https://www.hydra-cg.com/spec/latest/core/',
-    'urlprefix' => 'https://www.hydra-cg.com/spec/latest/core/#{db}:{accession}',
+    'url' => 'http://www.w3.org/ns/hydra/core',
+    'urlprefix' => 'http://www.w3.org/ns/hydra/core#{accession}',
   ));
   tripal_insert_cv(
     'hydra',
@@ -117,6 +119,18 @@ function tripal_chado_populate_vocab_HYDRA() {
  * Adds the RDFS database and terms.
  */
 function tripal_chado_populate_vocab_RDFS() {
+
+  tripal_insert_db(array(
+    'name' => 'rdf',
+    'description' => 'Resource Description Framework',
+    'url' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns',
+    'urlprefix' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+  ));
+  tripal_insert_cv(
+    'rdf',
+    'Resource Description Framework'
+  );
+
   tripal_insert_db(array(
     'name' => 'rdfs',
     'description' => 'Resource Description Framework Schema',
@@ -1506,7 +1520,13 @@ function tripal_chado_populate_vocab_TCONTACT() {
  */
 function tripal_chado_populate_vocab_TPUB() {
 
-  // No need to insert the TPUB cv/db those should already be added.
+  tripal_insert_db(array(
+    'name' => 'TPUB',
+    'description' => 'Tripal Publiation Ontology. A temporary ontology until a more formal appropriate ontology an be identified.',
+    'url' => 'http://localhost/cv/lookup/TPUB',
+    'urlprefix' => 'http://localhost/cv/lookup/TPUB/{accession}',
+  ));
+  tripal_insert_cv('tripal_pub', 'Tripal Publiation Ontology. A temporary ontology until a more formal appropriate ontology an be identified.');
 
   // make sure we have our supported databases
   tripal_insert_db(

+ 9 - 5
tripal_chado/includes/tripal_chado.vocab_storage.inc

@@ -29,17 +29,20 @@ function tripal_chado_vocab_get_vocabulary($vocabulary) {
     return FALSE;
   }
   $sql = "
-     SELECT DB.name as name, CV.name as short_name, DB.description, DB.url
+     SELECT DB.name as short_name, CV.name as name, DB.description, DB.url, DB.urlprefix
      FROM {db} DB
-      INNER JOIN {dbxref} DBX on DBX.db_id = DB.db_id
-      INNER JOIN {cvterm} CVT on CVT.dbxref_id = DBX.dbxref_id
-      INNER JOIN {cv} CV on CV.cv_id = CVT.cv_id
+      LEFT JOIN {dbxref} DBX on DBX.db_id = DB.db_id
+      LEFT JOIN {cvterm} CVT on CVT.dbxref_id = DBX.dbxref_id
+      LEFT JOIN {cv} CV on CV.cv_id = CVT.cv_id
      WHERE
       DB.name = :name
      LIMIT 1 OFFSET 0
   ";
   $result = chado_query($sql, array(':name' => $vocabulary));
   $result = $result->fetchAssoc();
+  if (!$result['name']) {
+    $result['name'] = $result['short_name'];
+  }
   return $result;
 }
 
@@ -75,7 +78,8 @@ function tripal_chado_vocab_get_term($vocabulary, $accession) {
       'name' => $cvterm->cv_id->name,
       'short_name' => $cvterm->dbxref_id->db_id->name,
       'description' =>  $cvterm->dbxref_id->db_id->description,
-      'url' => $cvterm->dbxref_id->db_id->url
+      'url' => $cvterm->dbxref_id->db_id->url,
+      'urlprefix' => $cvterm->dbxref_id->db_id->urlprefix
     ),
     'accession'  => $cvterm->dbxref_id->accession,
     'name'       => $cvterm->name,

+ 42 - 2
tripal_chado/tripal_chado.install

@@ -900,8 +900,8 @@ function tripal_chado_update_7305() {
     tripal_insert_db(array(
       'name' => 'hydra',
       'description' => 'A Vocabulary for Hypermedia-Driven Web APIs',
-      'url' => 'https://www.hydra-cg.com/spec/latest/core/',
-      'urlprefix' => 'https://www.hydra-cg.com/spec/latest/core/#{db}:{accession}',
+      'url' => 'http://www.w3.org/ns/hydra/core',
+      'urlprefix' => 'http://www.w3.org/ns/hydra/core#{accession}',
     ));
     tripal_insert_cv(
       'hydra',
@@ -1389,4 +1389,44 @@ function tripal_chado_update_7320() {
     $error = $e->getMessage();
     throw new DrupalUpdateException('Could not perform update: '. $error);
   }
+}
+
+/**
+ * Updating details for local ontologies.
+ */
+function tripal_chado_update_7321() {
+  try {
+    tripal_insert_db(array(
+      'name' => 'TPUB',
+      'description' => 'Tripal Publiation Ontology. A temporary ontology until a more formal appropriate ontology an be identified.',
+      'url' => 'http://localhost/cv/lookup/TPUB',
+      'urlprefix' => 'http://localhost/cv/lookup/TPUB/{accession}',
+    ));
+    tripal_insert_cv('tripal_pub', 'Tripal Publiation Ontology. A temporary ontology until a more formal appropriate ontology an be identified.');
+
+    tripal_insert_db(array(
+      'name' => 'rdf',
+      'description' => 'Resource Description Framework',
+      'url' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns',
+      'urlprefix' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+    ));
+    tripal_insert_cv(
+      'rdf',
+      'Resource Description Framework'
+    );
+    tripal_insert_db(array(
+      'name' => 'hydra',
+      'description' => 'A Vocabulary for Hypermedia-Driven Web APIs',
+      'url' => 'http://www.w3.org/ns/hydra/core',
+      'urlprefix' => 'http://www.w3.org/ns/hydra/core#{accession}',
+    ));
+    tripal_insert_cv(
+      'hydra',
+      'A Vocabulary for Hypermedia-Driven Web APIs.'
+    );
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
 }

+ 8 - 8
tripal_ws/includes/TripalWebService/TripalEntityService_v0_1.inc

@@ -1,5 +1,7 @@
 <?php
 
+use GraphAware\Bolt\Tests\Integration\ExceptionDispatchTest;
+
 class TripalEntityService_v0_1 extends TripalWebService {
 
   /**
@@ -10,10 +12,10 @@ class TripalEntityService_v0_1 extends TripalWebService {
   /**
    * A bit of text to describe what this service provides.
    */
-  public static $description = 'Provides acesss to the biological and
-    ancilliary data available on this site. Each content type represents
-    biological data that is defined in a controlled vocabulary (e.g.
-    Sequence Ontology term: gene (SO:0000704)).';
+  public static $description = 'Provides acesss to the biological and ' .
+    'ancilliary data available on this site. Each content type represents ' .
+    'biological data that is defined in a controlled vocabulary (e.g. ' .
+    'Sequence Ontology term: gene (SO:0000704)).';
 
   /**
    * A machine-readable type for this service. This name must be unique
@@ -887,12 +889,10 @@ class TripalEntityService_v0_1 extends TripalWebService {
       $member = new TripalWebServiceResource($service_path);
       $member->setID(urlencode($bundle->label));
 
-      // If the term has no URL then we'll default to using thie site's
-      // term lookup service.
+      // Make sure the term has a URL.
       $url = $term['url'];
       if (!$url) {
-        $url = url('cv/lookup/' . $term['vocabulary']['short_name'] . '/' . $term['accession'] , array('absolute' => TRUE));
-        $term['url'] = $url;
+        throw new Exception(t('Missing a URL for the term: @term.', array('@term' => $term['vocabulary']['short_name'] . ':' . $term['accession'])));
       }
       $this->setResourceType($member, $term);
       $this->addResourceProperty($member, $label, $bundle->label);

+ 207 - 108
tripal_ws/includes/TripalWebService/TripalVocabService_v0_1.inc

@@ -17,6 +17,30 @@ class TripalVocabService_v0_1 extends TripalWebService {
    */
   public static $type = 'vocab';
 
+  /**
+   * The list of web services.
+   */
+  private $services;
+
+
+  /**
+   * Constructor for the TripalVocabService_v0_1 class.
+   */
+  public function __construct($base_path) {
+    parent::__construct($base_path);
+
+    // Get the list of services provided by this Tripal site.
+    $this->services = tripal_get_web_services();
+    foreach ($this->services as $sindex => $service_class) {
+      // Load the class code.
+      tripal_load_include_web_service_class($service_class);
+
+      // Remove this class from the list of services.
+      if ($service_class::$type == 'vocab') {
+        unset($this->services[$sindex]);
+      }
+    }
+  }
 
   /**
    * @see TripalWebService::handleRequest()
@@ -24,133 +48,208 @@ class TripalVocabService_v0_1 extends TripalWebService {
   public function handleRequest() {
     $this->resource->addContextItem('vocab', $this->getServicePath() . '#');
 
-    $this->resource->addContextItem('ApiDocumentation', 'hydra:ApiDocumentation');
+    $this->resource->addContextItem('apiDocumentation', 'hydra:apiDocumentation');
     $this->resource->addContextItem('supportedClass', 'hydra:supportedClass');
-    $this->resource->setType('ApiDocumentation');
+    $this->resource->setType('apiDocumentation');
     $this->resource->setID('vocab/' . $this->getVersion());
 
-    // We need to list the content types as properties of the EntryPoint.
-    $properties = array();
-
 
-    // 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);
+    // Iterate through all of the web services and build their documentation
+    foreach ($this->services as $service_class) {
       $service = new $service_class($this->base_path);
       $supported_classes = $service->getSupportedClasses();
       foreach ($supported_classes as $supported) {
         $this->resource->addProperty('supportedClass', $supported);
       }
-      if ($service_class::$type != 'vocab') {
-        $event_prop = new TripalWebServiceResource($this->getServicePath());
-        $event_prop->addProperty('hydra:title', $service_class::$type);
-        $event_prop->addProperty('hydra:description', $service_class::$description);
-        $event_prop->addContextItem('required', 'hydra:reqiured');
-        $event_prop->addProperty('required', NULL);
-        $event_prop->addContextItem('readonly', 'hydra:readonly');
-        $event_prop->addProperty('readonly', TRUE);
-        $event_prop->addContextItem('writeonly', 'hydra:writeonly');
-        $event_prop->addProperty('writeonly', FALSE);
-        $event_prop->addContextItem('property', array(
-          "@id" => "hydra:property",
-          "@type" => "@id"
-        ));
-        //$event_prop->setID('');
-        //$event_prop->setType('');
-        $prop = new TripalWebServiceResource($this->getServicePath());
-        $prop->setID('vocab:EntryPoint/' . $service_class::$type);
-        $prop->setType('hydra:Link');
-        $prop->addContextItem('label', 'rdfs:label');
-        $prop->addProperty('label', $service_class::$label);
-        $prop->addContextItem('description', 'rdfs:comment');
-        $prop->addProperty('description', $service_class::$description);
-        $prop->addContextItem('domain', array(
-          "@id" => "rdfs:domain",
-          "@type" => "@id"
-        ));
-        $prop->addProperty('domain', 'vocab:EntryPoint');
-        $prop->addContextItem('range', array(
-          "@id" => "rdfs:range",
-          "@type" => "@id"
-        ));
-        $prop->addProperty('range', 'hydra:Collection');
-
-        $prop_ops = array();
-        $prop_op = new TripalWebServiceResource($this->getServicePath());
-        $prop_op->setID('_:event_collectionretrieve');
-        $prop_op->setType('hydra:Operation');
-        $prop_op->addContextItem('method', 'hydra:method');
-        $prop_op->addProperty('method', 'GET');
-        $prop_op->addContextItem('label', 'rdfs:label');
-        $prop_op->addProperty('label', 'Retrieves all ' . $service_class::$label . ' entities.');
-        $prop_op->addContextItem('description', 'rdfs:comment');
-        $prop_op->addProperty('description', NULL);
-        $prop_op->addContextItem('expects', array(
-          "@id" => "hydra:expects",
-          "@type" => "@id"
-        ));
-        $prop_op->addProperty('expects', NULL);
-        $prop_op->addContextItem('returns', array(
-          "@id" => "hydra:returns",
-          "@type" => "@id"
-        ));
-        $prop_op->addProperty('returns', 'hydra:Collection');
-        $prop_op->addContextItem('statusCodes', 'hydra:statusCodes');
-        $prop_op->addProperty('statusCodes', array());
-        $prop_ops[] = $prop_op;
-
-        $prop->addContextItem('supportedOperation', 'hydra:supportedOperation');
-        $prop->addProperty('supportedOperation', $prop_ops);
-        $event_prop->addProperty('property', $prop);
-        $properties[] = $event_prop;
-      }
     }
 
-    // Add in the generic supported classes.
-    $entry_class = new TripalWebServiceResource($this->base_path);
-    $entry_class->addContextItem('label', 'rdfs:label');
-    $entry_class->setID($this->getServicePath() . '#EntryPoint');
-    $entry_class->setType('hydra:Class');
-    $entry_class->addProperty('label', 'EntryPoint');
-    $entry_class->addContextItem('description', 'rdfs:comment');
-    $entry_class->addProperty('description', 'The main entry point or homepage of the API');
-    $entry_class->addContextItem('subClassOf', array(
+    // Now add the EntryPoint class.
+    $entry_point = $this->getEntryPointClass();
+    $this->resource->addProperty('supportedClass', $entry_point);
+
+    // 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('hydra:Collection');
+    $collection->setType('hydra:Class');
+    $collection->addProperty('hydra:title', '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
+    ));
+    $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
+    ));
+
+    $collection_ops = array();
+    $collection_op_get = new TripalWebServiceResource($this->base_path);
+    $collection_op_get->setID('_: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:Collection');
+    $collection_op_gets[] = $collection_op_get;
+
+    $collection->addProperty('supportedOperation', $collection_op_gets);
+
+    $this->resource->addProperty('supportedClass', $collection);
+  }
+
+  /**
+   * Generates the EntryPoint class for the API documents.
+   *
+   * @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_class->addProperty('subClassOf', NULL);
-    $entry_class->addContextItem('supportedOperation', 'hydra:supportedOperation');
-    $entry_class->addContextItem('supportedProperty', 'hydra:supportedProperty');
-
-
-    $operations = array();
-
-    $get_op = new TripalWebServiceResource($this->getServicePath());
-    $get_op->addContextItem('method', 'hydra:method');
-    $get_op->addProperty('method', 'GET');
-    $get_op->addContextItem('statusCodes', 'hydra:statusCodes');
-    $get_op->addProperty('statusCodes', array());
-    $get_op->addContextItem('label', 'rdfs:label');
-    $get_op->addProperty('label', "The APIs main entry point.");
-    $get_op->addContextItem('description', 'rdfs:comment');
-    $get_op->addProperty('description', NULL);
-    $get_op->addContextItem('expects', array(
+    $entry_point->addProperty('label', 'EntryPoint');
+    $entry_point->addProperty('description', 'The main entry point or homepage of the API');
+    $entry_point->addProperty('subClassOf', NULL);
+
+    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(
+        "@id" => "rdfs:domain",
+        "@type" => "@id"
+      ));
+      $service_prop_prop->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(
+        "@id" => "hydra:expects",
+        "@type" => "@id"
+      ));
+      $service_prop_prop_op_get->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', 'hydra:Collection');
+      $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,
+      );
+      $entry_properties[] = $service_prop;
+    }
+
+    $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"
     ));
-    $get_op->addProperty('expects', NULL);
-    $get_op->addContextItem('returns', array(
+    $service_op_get->addContextItem('returns', array(
       "@id" => "hydra:returns",
       "@type" => "@id"
     ));
-    $get_op->addProperty('returns', "vocab:EntryPoint");
-    $get_op->setID('_:entry_point');
-    $get_op->setType('hydra:Operation');
-
-    $operations[] = $get_op;
-    $entry_class->addProperty('supportedOperation', $operations);
-    $entry_class->addProperty('supportedProperty', $properties);
-    $this->resource->addProperty('supportedClass', $entry_class);
+    $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;
+
+    $entry_point->addProperty('supportedOperation', $service_ops);
+
+    return $entry_point;
   }
 }

+ 6 - 8
tripal_ws/includes/TripalWebServiceCollection.inc

@@ -54,12 +54,9 @@ class TripalWebServiceCollection extends TripalWebServiceResource {
     $this->members = array();
 
     // Add some terms to the context.
-    $term = tripal_get_term_details('hydra', 'Collection');
-    $this->addContextItem('Collection', $term['url']);
-    $term = tripal_get_term_details('hydra', 'totalItems');
-    $this->addContextItem('totalItems', $term['url']);
-    $term = tripal_get_term_details('hydra', 'member');
-    $this->addContextItem('member', $term['url']);
+    $this->addContextItem('Collection', 'hydra:Collection');
+    $this->addContextItem('totalItems', 'hydra:totalItems');
+    $this->addContextItem('member', 'hydra:member');
     parent::setType('Collection');
 
     // If the totalItems is set to -1 then this means paging is turned off and
@@ -163,7 +160,7 @@ class TripalWebServiceCollection extends TripalWebServiceResource {
     foreach ($this->members as $key => $member) {
       $member_data[] = $member->getData();
     }
-    $data['members'] = $member_data;
+    $data['member'] = $member_data;
 
     // If paging of this collection is enabled then add the pager control links.
 
@@ -193,4 +190,5 @@ class TripalWebServiceCollection extends TripalWebServiceResource {
     }
     return $context;
   }
-}
+}
+

+ 70 - 18
tripal_ws/includes/TripalWebServiceResource.inc

@@ -43,24 +43,43 @@ class TripalWebServiceResource {
 
     // First, add the RDFS and Hydra vocabularies to the context.  All Tripal
     // web services should use these.
-    $this->addContextItem('rdf', "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
-    $this->addContextItem('rdfs', 'http://www.w3.org/2000/01/rdf-schema#');
+    $vocab = tripal_get_vocabulary_details('rdf');
 
-    $this->addContextItem('hydra', "http://www.w3.org/ns/hydra/core#");
+    $this->addContextItem('rdf', $this->cleanVocabURL($vocab));
+
+    $vocab = tripal_get_vocabulary_details('rdfs');
+    $this->addContextItem('rdfs', $this->cleanVocabURL($vocab));
+
+    $vocab = tripal_get_vocabulary_details('hydra');
+    $url = $this->cleanVocabURL($vocab);
+    // $remove the '#' from the end of the URL as the HydraConsole automatically
+    // wants to add it.
+    //$url = preg_replace('/#/', '', $url);
+    $this->addContextItem('hydra', $url);
 
     $vocab = tripal_get_vocabulary_details('dc');
-    $this->addContextItem('dc', $vocab['url']);
+    $this->addContextItem('dc', $this->cleanVocabURL($vocab));
 
     $vocab = tripal_get_vocabulary_details('schema');
-    $this->addContextItem('schema', $vocab['url']);
+    $this->addContextItem('schema', $this->cleanVocabURL($vocab));
 
     $vocab = tripal_get_vocabulary_details('local');
-    $this->addContextItem('local', url($vocab['url'], array('absolute' => TRUE)). '/');
+    $this->addContextItem('local', url($vocab['urlprefix'], array('absolute' => TRUE)). '/');
 
 
     $this->data['@id'] = $service_path;
     $this->data['@type'] = '';
   }
+  /**
+   * A helper function to remove the {db} and {accession} from the URL prefix.
+   */
+  private function cleanVocabURL($vocab) {
+    $url = $vocab['urlprefix'];
+    $url = preg_replace('/{db}:{accession}/', '', $url);
+    $url = preg_replace('/{db}/', '', $url);
+    $url = preg_replace('/{accession}/', '', $url);
+    return $url;
+  }
 
   /**
    * Adds a term to the '@context' section for this resource.
@@ -157,6 +176,35 @@ class TripalWebServiceResource {
     }
   }
 
+  /**
+   * Checks the value to make sure there are no problems with.
+   *
+   * Will also expand any TriaplWebServiceResource by adding their
+   * context to this resource and substitute their data in place of the
+   * value.
+   *
+   * @param $value
+   *
+   */
+  private function checkValue(&$value) {
+    if (is_array($value)) {
+      foreach ($value as $i => $v) {
+        $this->checkValue($value[$i]);
+      }
+    }
+    else {
+      // If this is a TripalWebServiceResource then get it's data and use that
+      // as the new value.  Also, add in the elements context to this resource.
+      if (is_a($value, 'TripalWebServiceResource') or is_subclass_of($value, 'TripalWebServiceResource')) {
+        $context = $value->getContext();
+        foreach ($context as $k => $v) {
+          $this->addContextItem($k, $v);
+        }
+        $value = $value->getData();
+      }
+    }
+  }
+
 
   /**
    * Set's the unique identifier for the resource.
@@ -241,8 +289,9 @@ class TripalWebServiceResource {
   public function addProperty($key, $value) {
 
     $this->checkKey($key);
+    $this->checkValue($value);
 
-    // If this is a numeric array then go through each element and add them.
+    // If this is a numeric keyed array then add each item.
     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();
@@ -253,17 +302,6 @@ class TripalWebServiceResource {
       return;
     }
 
-    // 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();
-    }
-
     // 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;
@@ -282,6 +320,20 @@ class TripalWebServiceResource {
     }
   }
 
+  /**
+   * Retrieves the value of a property.
+   *
+   * @param $key
+   *   The name of the property.
+   *
+   * @param
+   *   The value of the property. This could be a scalar, array or
+   *   a TripalWebService object.
+   */
+  function getProperty($key) {
+    return $this->data[$key];
+  }
+
   /**
    * Retrieves the data section of the resource.
    *

+ 0 - 1064
tripal_ws/includes/tripal_ws.rest_v0.1.inc

@@ -1,1064 +0,0 @@
-<?php
-
-
-
-/**
- *
- */
-function tripal_ws_services_v0_1($api_url, $ws_path, $params) {
-
-  // Set some initial variables.
-  $response = array();
-  $version = 'v0.1';
-
-  // Set some defaults for the response.
-  $response['@context'] =  array();
-
-  // Lump everything ito a try block so that if there is a problem we can
-  // throw an error and have that returned in the response.
-  try {
-
-    // The services is the first argument
-    $service = (count($ws_path) > 0) ? $ws_path[0] : '';
-
-    switch ($service) {
-      case 'doc':
-        tripal_ws_services_v0_1_handle_doc_service($api_url, $response);
-        break;
-      case 'content':
-        tripal_ws_services_v0_1_handle_content_service($api_url, $response, $ws_path, $params);
-        break;
-      case 'vocab':
-        tripal_ws_services_v0_1_handle_vocab_service($api_url, $response, $ws_path);
-        break;
-      default:
-        tripal_ws_services_v0_1_handle_no_service($api_url, $response);
-    }
-  }
-  catch (Exception $e) {
-    watchdog('tripal_ws', $e->getMessage(), array(), WATCHDOG_ERROR);
-    $message = $e->getMessage();
-    drupal_add_http_header('Status', '400 Bad Request');
-
-  }
-
-  return $response;
-}
-
-
-/**
- *
- * @param $api_url
- * @param $response
- * @param $ws_path
- */
-function tripal_ws_services_v0_1_handle_content_service($api_url, &$response, $ws_path, $params) {
-
-  // Get the content type.
-  $ctype     = (count($ws_path) > 1) ? $ws_path[1] : '';
-  $entity_id = (count($ws_path) > 2) ? $ws_path[2] : '';
-
-  // If we have no content type then list all of the available content types.
-  if (!$ctype) {
-    tripal_ws_services_v0_1_get_content_types($api_url, $response);
-  }
-  // If we don't have an entity ID then show a paged list of entities with
-  // the given type.
-  else if ($ctype and !$entity_id) {
-   tripal_ws_services_v0_1_get_content_type($api_url, $response, $ws_path, $ctype, $params);
-  }
-  // If we have a content type and an entity ID then show the entity
-  else {
-    tripal_ws_services_v0_1_get_content($api_url, $response, $ws_path, $ctype, $entity_id, $params);
-  }
-}
-/**
-*
-* @param $api_url
-* @param $response
-* @param $ws_path
-*/
-function tripal_ws_services_v0_1_handle_vocab_service($api_url, &$response, $ws_path) {
-
-  // Get the vocab name.
-  $vocabulary = (count($ws_path) > 1) ? $ws_path[1] : '';
-  $accession = (count($ws_path) > 2) ? $ws_path[2] : '';
-
-  // If we have no $vocabulary type then list all of the available vocabs.
-  if (!$vocabulary) {
-    tripal_ws_services_v0_1_get_vocabs($api_url, $response);
-  }
-  // If we don't have a $vocabulary then show a paged list of terms.
-  else if ($vocabulary and !$accession) {
-    tripal_ws_services_v0_1_get_vocab($api_url, $response, $ws_path, $vocabulary);
-  }
-  // If we have a content type and an entity ID then show the entity
-  else if ($vocabulary and $accession) {
-    tripal_ws_services_v0_1_get_term($api_url, $response, $ws_path, $vocabulary, $accession);
-  }
-}
-
-/**
- *
- * @param $api_url
- * @param $response
- */
-function tripal_ws_services_v0_1_get_vocabs($api_url, &$response) {
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-
-  // Next add in the ID for tihs resource.
-  $response['@id'] = url($api_url . '/vocab', array('absolute' => TRUE));
-
-  // Start the list.
-  $response['@type'] = 'Collection';
-  $response['totalItems'] = 0;
-  $response['label'] = 'Content Types';
-  $response['member'] = array();
-
-  $vocabs = db_select('tripal_vocab', 'tv')
-    ->fields('tv')
-    ->execute();
-  // Iterate through the vocabularies and add an entry in the collection.
-  $i = 0;
-  while ($vocab = $vocabs->fetchObject()) {
-    // Add the bundle as a content type.
-    $response['member'][] = array(
-      '@id' => url($api_url . '/vocab/' . urlencode($vocab->vocabulary), array('absolute' => TRUE)),
-      '@type' => 'vocabulary',
-      'vocabulary' => $vocab->vocabulary,
-    );
-    $i++;
-  }
-  $response['totalItems'] = $i;
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['Collection'] = 'hydra:Collection';
-  $response['@context']['totalItems'] = 'hydra:totalItems';
-  $response['@context']['member'] = 'hydra:member';
-  $response['@context']['label'] = 'rdfs:label';
-  $response['@context']['description'] = 'hydra:description';
-
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-}
-
-/**
- *
- * @param $api_url
- * @param $response
- * @param $ws_path
- */
-function tripal_ws_services_v0_1_get_vocab($api_url, &$response, $ws_path, $vocabulary) {
-
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-  $response['@context']['schema'] = 'https://schema.org/';
-
-  // Next add in the ID for tihs resource.
-  $response['@id'] = url($api_url . '/vocab/' . $vocabulary, array('absolute' => TRUE));
-
-  // Get the vocabulary
-  $vocab = tripal_load_vocab_entity(array('vocabulary' => $vocabulary));
-
-  // Start the list.
-  $response['@type'] = 'Collection';
-  $response['totalItems'] = 0;
-  $response['label'] = vocabulary . " vocabulary collection";
-  $response['comment'] = 'The following list of terms may not be the full ' .
-      'list for the vocabulary.  The terms listed here are only those ' .
-      'that have associated content on this site.';
-
-  // Get the list of terms for this vocab.
-  $query = db_select('tripal_term', 'tt')
-    ->fields('tt', array('id'))
-    ->condition('vocab_id', $vocab->id)
-    ->orderBy('accession', 'DESC');
-
-  // Iterate through the entities and add them to the list.
-  $terms = $query->execute();
-  $i = 0;
-  while($term = $terms->fetchObject()) {
-    $term = tripal_load_term_entity(array('term_id' => $term->id));
-    $response['member'][] = array(
-      '@id' => url($api_url . '/vocab/' . urlencode($vocabulary) . '/' .  urlencode($term->accession), array('absolute' => TRUE)),
-      '@type' => 'vocabulary_term',
-      'vocabulary' => $vocab->vocabulary,
-      'accession' => $term->accession,
-      'name' => $term->name,
-      'definition' => $term->definition,
-    );
-    $i++;
-  }
-  $response['totalItems'] = $i;
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['Collection'] = 'hydra:Collection';
-  $response['@context']['totalItems'] = 'hydra:totalItems';
-  $response['@context']['member'] = 'hydra:member';
-  $response['@context']['label'] = 'rdfs:label';
-  $response['@context']['comment'] = 'rdfs:comment';
-  $response['@context']['itemPage'] = 'schema:itemPage';
-
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-
-}
-
-/**
- *
- * @param $api_url
- * @param $response
- * @param $ws_path
- */
-function tripal_ws_services_v0_1_get_term($api_url, &$response, $ws_path, $vocabulary, $accession) {
-
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-  $response['@context']['schema'] = 'https://schema.org/';
-
-  // Get the term.
-  $term = tripal_load_term_entity(array('vocabulary' => $vocabulary, 'accession' => $accession));
-
-  // Next add in the ID and Type for this resources.
-  $response['@id'] = url($api_url . '/vocab/' . urlencode($vocabulary) . '/' . urlencode($accession), array('absolute' => TRUE));
-  $response['@type'] = 'vocabulary_term';
-  $response['label'] = $term->name;
-  $response['vocabulary'] = $vocabulary;
-  $response['accession'] = $accession;
-  $response['name'] = $term->name;
-  $response['definition'] = $term->definition;
-
-  if ($term->url) {
-    $response['URL'] = $term->url;
-  }
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['label'] = 'rdfs:label';
-  $response['@context']['itemPage'] = 'schema:itemPage';
-
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-}
-/**
- * Provides a collection (list) of all of the content types.
- *
- * @param $api_url
- * @param $response
- */
-function tripal_ws_services_v0_1_get_content_types($api_url, &$response) {
-
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-
-  // Next add in the ID for tihs resource.
-  $response['@id'] = url($api_url . '/content', array('absolute' => TRUE));
-
-  // Start the list.
-  $response['@type'] = 'Collection';
-  $response['totalItems'] = 0;
-  $response['label'] = 'Content Types';
-  $response['member'] = 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;
-
-    $response['@context'][$term->name] = $term->url;
-
-    // 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;
-    }
-    // Add the bundle as a content type.
-    $response['member'][] = array(
-      '@id' => url($api_url . '/content/' . urlencode($bundle->label), array('absolute' => TRUE)),
-      '@type' => $term->name,
-      'label' => $bundle->label,
-      'description' => $description,
-    );
-    $i++;
-  }
-  $response['totalItems'] = $i;
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['Collection'] = 'hydra:Collection';
-  $response['@context']['totalItems'] = 'hydra:totalItems';
-  $response['@context']['member'] = 'hydra:member';
-  $response['@context']['label'] = 'rdfs:label';
-  $response['@context']['description'] = 'hydra:description';
-
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-}
-
-/**
- *
- * @param $api_url
- * @param $response
- * @param $ws_path
- */
-function tripal_ws_services_v0_1_get_content_type($api_url, &$response, $ws_path, $ctype, $params) {
-
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-
-  // Next add in the ID for this resource.
-  $URL = url($api_url . '/content/' . $ctype, array('absolute' => TRUE));
-  $response['@id'] = $URL;
-
-  // Get the TripalBundle, TripalTerm and TripalVocab type for this type.
-  $bundle = tripal_load_bundle_entity(array('label' => $ctype));
-  $term = entity_load('TripalTerm', array('id' => $bundle->term_id));
-  $term = reset($term);
-  $response['@context'][$term->name] = $term->url;
-
-  // Start the list.
-  $response['@type'] = 'Collection';
-  $response['totalItems'] = 0;
-  $response['label'] = $bundle->label . " collection";
-
-  // Iterate through the fields and create a $field_mapping array that makes
-  // it easier to determine which filter criteria belongs to which field. The
-  // key is the label for the field and the value is the field name. This way
-  // user's can use the field label or the field name to form a query.
-  $field_mapping = array();
-  $fields = field_info_fields();
-  foreach ($fields as $field) {
-    if (array_key_exists('TripalEntity', $field['bundles'])) {
-      foreach ($field['bundles']['TripalEntity'] as $bundle_name) {
-        if ($bundle_name == $bundle->name) {
-          $instance = field_info_instance('TripalEntity', $field['field_name'], $bundle_name);
-          if (array_key_exists('term_accession', $instance['settings'])){
-            $vocabulary = $instance['settings']['term_vocabulary'];
-            $accession = $instance['settings']['term_accession'];
-            $term = tripal_get_term_details($vocabulary, $accession);
-            $key = $term['name'];
-            $key = strtolower(preg_replace('/ /', '_', $key));
-            $field_mapping[$key] = $field['field_name'];
-            $field_mapping[$field['field_name']] = $field['field_name'];
-          }
-        }
-      }
-    }
-  }
-
-  // Convert the filters to their field names
-  $new_params = array();
-  $order = array();
-  $order_dir = array();
-  $URL_add = array();
-  foreach ($params as $param => $value) {
-    $URL_add[] = "$param=$value";
-
-    // Ignore non filter parameters
-    if ($param == 'page' or $param == 'limit') {
-      continue;
-    }
-
-    // Handle order separately
-    if ($param == 'order') {
-      $temp = explode(',', $value);
-      foreach ($temp as $key) {
-        $matches = array();
-        $dir = 'ASC';
-        // The user can provide a direction by separating the field key and the
-        // direction with a '|' character.
-        if (preg_match('/^(.*)\|(.*)$/', $key, $matches)) {
-          $key = $matches[1];
-          if ($matches[2] == 'ASC' or $matches[2] == 'DESC') {
-            $dir = $matches[2];
-          }
-          else {
-            // TODO: handle error of providing an incorrect direction.
-          }
-        }
-        if (array_key_exists($key, $field_mapping)) {
-          $order[$field_mapping[$key]] = $key;
-          $order_dir[] = $dir;
-        }
-        else {
-          // TODO: handle error of providing a non existing field name.
-        }
-      }
-      continue;
-    }
-
-    // Break apart any operators
-    $key = $param;
-    $op = '=';
-    $matches = array();
-    if (preg_match('/^(.+);(.+)$/', $key, $matches)) {
-      $key = $matches[1];
-      $op = $matches[2];
-    }
-
-    // Break apart any subkeys and pull the first one out for the term name key.
-    $subkeys = explode(',', $key);
-    if (count($subkeys) > 0) {
-      $key = array_shift($subkeys);
-    }
-    $column_name = $key;
-
-    // Map the values in the filters to their appropriate field names.
-    if (array_key_exists($key, $field_mapping)) {
-      $field_name = $field_mapping[$key];
-      if (count($subkeys) > 0) {
-        $column_name .= '.' . implode('.', $subkeys);
-      }
-      $new_params[$field_name]['value'] = $value;
-      $new_params[$field_name]['op'] = $op;
-      $new_params[$field_name]['column'] = $column_name;
-    }
-    else {
-      throw new Exception("The filter term, '$key', is not available for use.");
-    }
-  }
-
-  // Get the list of entities for this bundle.
-  $query = new TripalFieldQuery();
-  $query->entityCondition('entity_type', 'TripalEntity');
-  $query->entityCondition('bundle', $bundle->name);
-  foreach($new_params as $field_name => $details) {
-    $value = $details['value'];
-    $column_name = $details['column'];
-    switch ($details['op']) {
-      case 'eq':
-        $op = '=';
-        break;
-      case 'gt':
-        $op = '>';
-        break;
-      case 'gte':
-        $op = '>=';
-        break;
-      case 'lt':
-        $op = '<';
-        break;
-      case 'lte':
-        $op = '<=';
-        break;
-      case 'ne':
-        $op = '<>';
-        break;
-      case 'contains':
-        $op = 'CONTAINS';
-        break;
-      case 'starts':
-        $op = 'STARTS WITH';
-        break;
-      default:
-        $op = '=';
-    }
-
-    //print_r(array($field_name, $column_name, $value, $op));
-
-    // We pass in the $column_name as an identifier for any sub fields
-    // that are present for the fields.
-    $query->fieldCondition($field_name, $column_name, $value, $op);
-  }
-
-  // Perform the query just as a count first to get the number of records.
-  $cquery = clone $query;
-  $cquery->count();
-  $num_records = $cquery->execute();
-  $num_records = count($num_records['TripalEntity']);
-
-  if (!$num_records) {
-    $num_records = 0;
-  }
-
-  // Add in the pager to the response.
-  $response['totalItems'] = $num_records;
-  $limit = array_key_exists('limit', $params) ? $params['limit'] : 25;
-
-  $total_pages = ceil($num_records / $limit);
-  $page = array_key_exists('page', $params) ? $params['page'] : 1;
-  if ($num_records > 0) {
-    $response['view'] = array(
-      '@id' => $URL . '?' . implode('&', array_merge($URL_add, array("page=$page", "limit=$limit"))),
-      '@type' => 'PartialCollectionView',
-      'first' => $URL . '?' . implode('&', array_merge($URL_add, array("page=1", "limit=$limit"))),
-      'last' => $URL . '?' . implode('&', array_merge($URL_add, array("page=$total_pages", "limit=$limit"))),
-    );
-    $prev = $page - 1;
-    $next = $page + 1;
-    if ($prev > 0) {
-      $response['view']['previous'] = $URL . '?' . implode('&', array_merge($URL_add, array("page=$prev", "limit=$limit")));
-    }
-    if ($next < $total_pages) {
-      $response['view']['next'] = $URL . '?' . implode('&', array_merge($URL_add, array("page=$next", "limit=$limit")));
-    }
-  }
-
-  // Set the query order
-  $order_keys = array_keys($order);
-  for($i = 0; $i < count($order_keys); $i++) {
-    $query->fieldOrderBy($order_keys[$i], $order[$order_keys[$i]], $order_dir[$i]);
-  }
-
-  // Set the query range
-  $start = ($page - 1) * $limit;
-  $query->range($start, $limit);
-
-  // Now perform the query.
-  $results = $query->execute();
-
-  // Iterate through the entities and add them to the list.
-  $i = 0;
-  foreach ($results['TripalEntity'] as $entity_id => $stub) {
-    $vocabulary = '';
-    $term_name = '';
-
-    // We don't need all of the attached fields for an entity so, we'll
-    // not use the entity_load() function.  Instead just pull it from the
-    // database table.
-    $query = db_select('tripal_entity', 'TE');
-    $query->join('tripal_term', 'TT', 'TE.term_id = TT.id');
-    $query->fields('TE');
-    $query->fields('TT', array('name'));
-    $query->condition('TE.id', $entity_id);
-    $entity = $query->execute()->fetchObject();
-
-    //$entity = tripal_load_entity('TripalEntity', array($entity->id));
-    $response['member'][] = array(
-      '@id' => url($api_url . '/content/' . urlencode($ctype) . '/' .  $entity->id, array('absolute' => TRUE)),
-      '@type' => $entity->name,
-      'label' => $entity->title,
-      'itemPage' => url('/bio_data/' . $entity->id, array('absolute' => TRUE)),
-    );
-    $i++;
-  }
-
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['Collection'] = 'hydra:Collection';
-  $response['@context']['totalItems'] = 'hydra:totalItems';
-  $response['@context']['member'] = 'hydra:member';
-  $response['@context']['label'] = 'rdfs:label';
-  $response['@context']['itemPage'] = 'schema:itemPage';
-
-//   $response['operation'][] = array(
-//     '@type' => 'hydra:CreateResourceOperation',
-//     'hydra:method' => 'PUT'
-//   );
-
-//   $response['query'] = array(
-//     '@id' => $response['@id'],
-//     '@type' => 'IriTemplate',
-//     "template" => $response['@id'] . "{?name,}",
-//     "mapping" => array(
-//       array(
-//         "hydra:variable" => 'name',
-//         "hydra:property" => 'name',
-//       )
-//     )
-//   );
-
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-}
-
-/**
- *
- * @param unknown $response
- * @param unknown $ws_path
- * @param unknown $ctype
- * @param unknown $entity_id
- * @param unknown $params
- */
-function tripal_ws_services_v0_1_get_content_add_fields($entity, $bundle, $api_url, &$response, $ws_path, $ctype, $entity_id, $params) {
-
-
-  // Get information about the fields attached to this bundle and sort them
-  // in the order they were set for the display.
-  $instances = field_info_instances('TripalEntity', $bundle->name);
-
-  uasort($instances, function($a, $b) {
-    $a_weight = (is_array($a) && isset($a['widget']['weight'])) ? $a['widget']['weight'] : 0;
-    $b_weight = (is_array($b) && isset($b['widget']['weight'])) ? $b['widget']['weight'] : 0;
-
-    if ($a_weight == $b_weight) {
-      return 0;
-    }
-    return ($a_weight < $b_weight) ? -1 : 1;
-  });
-
-  // Iterate through the fields and add each value to the response.
-  //$response['fields'] = $fields;
-  foreach ($instances as $field_name => $instance) {
-
-    // Ignore the content_type field provided by Tripal.
-    if ($field_name == 'content_type') {
-      continue;
-    }
-
-    // Skip hidden fields.
-    if ($instance['display']['default']['type'] == 'hidden') {
-      continue;
-    }
-
-    // Get the information about this field. It will have settings different
-    // from the instance.
-    $field = field_info_field($field_name);
-
-    // By default, the label for the key in the output should be the
-    // term from the vocabulary that the field is assigned. But in the
-    // case that the field is not assigned a term, we must use the field name.
-    $field_name = $instance['field_name'];
-    $vocabulary = $instance['settings']['term_vocabulary'];
-    $accession = $instance['settings']['term_accession'];
-    $term = tripal_get_term_details($vocabulary, $accession);
-    if ($term) {
-      $key = $term['name'];
-      $key_adj = strtolower(preg_replace('/ /', '_', $key));
-      // The term schema:url also points to a recource so we need
-      // to make sure we set the type to be '@id'.
-      if ($vocabulary == 'schema' and $accession == 'url') {
-        $response['@context'][$key_adj] = array(
-          '@id' => $term['url'],
-          '@type' => '@id',
-        );
-      }
-      else {
-        $response['@context'][$key_adj] = $term['url'];
-      }
-    }
-    else {
-      continue;
-    }
-
-    // If this field should not be attached by default then just add a link
-    // so that the caller can get the information separately.
-    $instance_settings = $instance['settings'];
-    if (array_key_exists('auto_attach', $instance['settings']) and
-        $instance_settings['auto_attach'] == FALSE) {
-      $response['@context'][$key_adj] = array(
-        '@id' => $response['@context'][$key_adj],
-        '@type' => '@id'
-      );
-      // Add a URL only if there are values. If there are no values then
-      // don't add a URL which would make the end-user think they can get
-      // that information.
-      $items = field_get_items('TripalEntity', $entity, $field_name);
-      if ($items and count($items) > 0 and $items[0]['value']) {
-        $response[$key_adj] = url($api_url . '/content/' . $ctype . '/' . $entity->id . '/' . urlencode($key), array('absolute' => TRUE));
-      }
-      else {
-        $response[$key_adj] = NULL;
-      }
-
-      continue;
-    }
-
-    // Get the details for this field for the JSON-LD response.
-    tripal_ws_services_v0_1_get_content_add_field($key_adj, $entity, $field, $instance, $api_url, $response);
-  }
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['label'] = 'https://www.w3.org/TR/rdf-schema/#ch_label';
-  $response['@context']['itemPage'] = 'https://schema.org/ItemPage';
-
-  //   $response['operation'][] = array(
-  //     '@type' => 'hydra:DeleteResourceOperation',
-  //     'hydra:method' => 'DELETE'
-  //   );
-  //   $response['operation'][] = array(
-  //     '@type' => 'hydra:ReplaceResourceOperation',
-  //     'hydra:method' => 'POST'
-  //   );
-}
-/**
- *
- * @param unknown $field_arg
- * @param unknown $api_url
- * @param unknown $response
- * @param unknown $ws_path
- * @param unknown $ctype
- * @param unknown $entity_id
- * @param unknown $params
- */
-function tripal_ws_services_v0_1_get_content_find_field($field_arg, $ctype, $entity_id) {
-
-  $bundle = tripal_load_bundle_entity(array('label' => $ctype));
-  $entity = tripal_load_entity('TripalEntity', array('id' => $entity_id));
-  $entity = reset($entity);
-  $term = NULL;
-
-  // Find the field whose term matches the one provied.
-  $value = array();
-  $instances = field_info_instances('TripalEntity', $bundle->name);
-  foreach ($instances as $instance) {
-    $field_name = $instance['field_name'];
-    $field = field_info_field($field_name);
-    $vocabulary = $instance['settings']['term_vocabulary'];
-    $accession = $instance['settings']['term_accession'];
-    $temp_term = tripal_get_term_details($vocabulary, $accession);
-    if ($temp_term['name'] == $field_arg) {
-      return array($entity, $bundle, $field, $instance, $temp_term);
-    }
-  }
-}
-/**
- *
- * @param unknown $api_url
- * @param unknown $response
- * @param unknown $ws_path
- * @param unknown $ctype
- * @param unknown $entity_id
- * @param unknown $params
- * @return number
- */
-function tripal_ws_services_v0_1_get_content($api_url, &$response, $ws_path, $ctype, $entity_id, $params) {
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-
-  // If we have an argument in the 4th element (3rd index) then the user
-  // is requesting to expand the details of a field that was not
-  // initially attached to the enity.
-  $field_arg = '';
-  if (array_key_exists(3, $ws_path)) {
-
-    $field_arg = urldecode($ws_path[3]);
-    list($entity, $bundle, $field, $instance, $term) = tripal_ws_services_v0_1_get_content_find_field($field_arg, $ctype, $entity_id);
-
-    // If we couldn't match this field argument to a field and entity then return
-    if (!$entity or !$field) {
-      return;
-    }
-
-    // Next add in the ID and Type for this resources.
-    $key = $term['name'];
-    $key_adj = strtolower(preg_replace('/ /', '_', $term['name']));
-    $response['@context'][$key_adj] = $term['url'];
-    $response['@id'] = url($api_url . '/content/' . $ctype . '/' . $entity->id . '/' . urlencode($key), array('absolute' => TRUE));
-
-    // Attach the field and then add it's values to the response.
-    field_attach_load($entity->type, array($entity->id => $entity),
-      FIELD_LOAD_CURRENT, array('field_id' => $field['id']));
-
-    tripal_ws_services_v0_1_get_content_add_field($key_adj, $entity, $field, $instance, $api_url, $response, TRUE);
-    tripal_ws_services_v0_1_write_context($response, $ctype);
-    return;
-  }
-
-  // If we don't have a 4th argument then we're loading the base record.
-  // Get the TripalBundle, TripalTerm and TripalVocab type for this type.
-  $bundle = tripal_load_bundle_entity(array('label' => $ctype));
-  $term = entity_load('TripalTerm', array('id' => $bundle->term_id));
-  $term = reset($term);
-  $vocab = $term->vocab;
-
-  // Add the vocabulary for this content type to the @context section.
-  if (!array_key_exists($vocab->vocabulary, $response['@context'])) {
-    // If there is no URL prefix then use this API's vocabulary API
-    if (property_exists($term, 'urlprefix')) {
-      $response['@context'][$vocab->vocabulary] = $term->urlprefix;
-    }
-    else {
-      $response['@context'][$vocab->vocabulary] = url($api_url . '/vocab/' . $vocab->vocabulary . '/', array('absolute' => TRUE));
-    }
-  }
-
-  // Get the TripalEntity
-  $entity = tripal_load_entity('TripalEntity', array('id' => $entity_id));
-  $entity = reset($entity);
-
-  // Next add in the ID and Type for this resources.
-  $response['@id'] = url($api_url . '/content/' . $ctype . '/' . $entity_id, array('absolute' => TRUE));
-  $response['@type'] = $term->name;
-  $response['@context'][$term->name] = $term->url;
-  $response['label'] = $entity->title;
-  $response['itemPage'] = url('/bio_data/' . $entity->id, array('absolute' => TRUE));
-
-  tripal_ws_services_v0_1_get_content_add_fields($entity, $bundle, $api_url, $response, $ws_path, $ctype, $entity_id, $params);
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-}
-
-/**
- *
- * @param $response
- * @param $ctype
- */
-function tripal_ws_services_v0_1_write_context(&$response, $ctype) {
-  // Save the response '@context' into a temporary file
-  $context = array('@context' => $response['@context']);
-  $file_name = drupal_tempnam(file_default_scheme() . '://', 'tws_context-') . '.json';
-  $context_file = file_save_data(json_encode($context), $file_name, FILE_EXISTS_REPLACE );
-
-  // Mark the file as temporary by setting it's status
-  $context_file->status = 0;
-  file_save($context_file);
-
-  // Return the response with the '@context' section replaced with the file URL.
-  $response['@context'] = file_create_url($context_file->uri);
-
-}
-
-/**
- *
- */
-function tripal_ws_services_v0_1_get_content_add_field($key, $entity, $field, $instance, $api_url, &$response, $is_field_page = NULL) {
-  // Get the field  settings.
-  $field_name = $field['field_name'];
-  $field_settings = $field['settings'];
-
-  $items = field_get_items('TripalEntity', $entity, $field_name);
-  if (!$items) {
-    return;
-  }
-
-  // Give modules the opportunity to edit values for web services. This hook
-  // really should be used sparingly. Where it helps is with non Tripal fields
-  // that are added to a TripalEntity content type and it doesn't follow
-  // the rules (e.g. Image field).
-  drupal_alter('tripal_ws_value', $items, $field, $instance);
-
-  $values = array();
-  for ($i = 0; $i < count($items); $i++) {
-    $values[$i] = tripal_ws_services_v0_1_rewrite_field_items_keys($items[$i]['value'], $response, $api_url);
-  }
-
-  // If the field cardinality is 1
-  if ($field[cardinality] == 1) {
-    // If the value is an array and this is the field page then all of those
-    // key/value pairs should be added directly to the response.
-    if (is_array($values[0])) {
-      if ($is_field_page) {
-        foreach ($values[0] as $k => $v) {
-          $response[$k] = $v;
-        }
-      }
-      else {
-        $response[$key] = $values[0];
-      }
-    }
-    // If the value is not an array it's a scalar so add it as is to the
-    // response.
-    else {
-      $response[$key] = $values[0];
-    }
-  }
-
-  // If the field cardinality is > 1
-  if ($field[cardinality] != 1) {
-
-    // If this is the field page then the Collection is added directly to the
-    // response, otherwise, it's added under the field $key.
-    if ($is_field_page) {
-      $response['@type'] = 'Collection';
-      $response['totalItems'] = count($values);
-      $response['label'] = $instance['label'];
-      $response['member'] = $values;
-    }
-    else {
-      $response[$key] = array(
-        '@type' => 'Collection',
-        'totalItems' => count($values),
-        'label' => $instance['label'],
-        'member' => $values,
-      );
-    }
-  }
-}
-/**
- *
- */
-function tripal_ws_services_v0_1_rewrite_field_items_keys($value, &$response, $api_url) {
-
-  $new_value = '';
-  // If the value is an array rather than a scalar then map the sub elements
-  // to controlled vocabulary terms.
-  if (is_array($value)) {
-    $temp = array();
-    foreach ($value as $k => $v) {
-      $matches = array();
-      if (preg_match('/^(.+):(.+)$/', $k, $matches)) {
-        $vocabulary = $matches[1];
-        $accession = $matches[2];
-        $term = tripal_get_term_details($vocabulary, $accession);
-        $key_adj = strtolower(preg_replace('/ /', '_', $term['name']));
-        if (is_array($v)) {
-          $temp[$key_adj] = tripal_ws_services_v0_1_rewrite_field_items_keys($v, $response, $api_url);
-        }
-        else {
-          $temp[$key_adj] = $v !== "" ? $v : NULL;
-        }
-        // The term schema:url also points to a recource so we need
-        // to make sure we set the type to be '@id'.
-        if ($vocabulary == 'schema' and $accession == 'url') {
-          $response['@context'][$key_adj] = array(
-            '@id' => $term['url'],
-            '@type' => '@id',
-          );
-        }
-        else {
-          $response['@context'][$key_adj] = $term['url'];
-        }
-      }
-      else {
-        $temp[$k] = $v;
-      }
-    }
-    $new_value = $temp;
-
-    // Recurse through the values array and set the entity elements
-    // and add the fields to the context.
-    tripal_ws_services_v0_1_rewrite_field_items_entity($new_value, $response, $api_url);
-
-  }
-  else {
-    $new_value = $value !== "" ? $value : NULL;
-  }
-
-  return $new_value;
-}
-/**
- *
- */
-function tripal_ws_services_v0_1_rewrite_field_items_entity(&$items, &$response, $api_url) {
-
-  if (!$items) {
-    return;
-  }
-  foreach ($items as $key => $value) {
-    if (is_array($value)) {
-      tripal_ws_services_v0_1_rewrite_field_items_entity($items[$key], $response, $api_url);
-      continue;
-    }
-
-    if ($key == 'entity') {
-      list($item_etype, $item_eid) = explode(':', $items['entity']);
-      if ($item_eid) {
-        $item_entity = tripal_load_entity($item_etype, array($item_eid));
-        $item_entity = reset($item_entity);
-        $bundle = tripal_load_bundle_entity(array('name' => $item_entity->bundle));
-        $items['@id'] = url($api_url . '/content/' . $bundle->label . '/' . $item_eid, array('absolute' => TRUE));
-      }
-      unset($items['entity']);
-    }
-  }
-}
-
-/**
- * Provides the Hydra compatible apiDocumentation page that describes this API.
- *
- * @param $api_url
- * @param $response
- */
-function tripal_ws_services_v0_1_handle_doc_service($api_url, &$response) {
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-
-  // Next add in the ID for tihs resource.
-  $site_name = variable_get('site_name', '');
-  $response['@id'] = url($api_url . '/doc/', array('absolute' => TRUE));
-  $response['title'] =  $site_name . ": RESTful Web Services API";
-  $response['entrypoint'] = url($api_url, array('absolute' => TRUE));
-  $response['description'] = "A fully queryable REST API using JSON-LD and " .
-      "discoverable using the WC3 Hydra specification.";
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['title'] = 'hydra:title';
-  $response['@context']['entrypoint'] = array(
-    "@id" => "hydra:entrypoint",
-    "@type" => "@id",
-  );
-  $response['@context']['description'] = 'hydra:description';
-
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-}
-
-/**
- * This function specifies the types of resources avaiable via the API.
- *
- * @param $api_url
- * @param $response
- * @param $ws_path
- */
-function tripal_ws_services_v0_1_handle_no_service($api_url, &$response) {
-
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-  $response['@context']['dc'] = 'http://purl.org/dc/dcmitype/';
-  $response['@context']['schema'] = 'https://schema.org/';
-
-  // Next add in the ID for tihs resource.
-  $response['@id'] = url($api_url, array('absolute' => TRUE));
-
-  // Start the list.
-  $response['@type'] = 'Collection';
-  $response['totalItems'] = 0;
-  $response['label'] = 'Services';
-  $response['member'] = array();
-
-  // Start the list.
-  $response['member'][] = array(
-    '@id' => url($api_url . '/content/', array('absolute' => TRUE)),
-    '@type' => 'Service',
-    'label' => 'Content Types',
-    'description' => 'Provides acesss to the biological and ' .
-    'ancilliary data available on this site. Each content type ' .
-    'represents biological data that is defined in a controlled vocabulary '.
-    '(e.g. Sequence Ontology term: gene (SO:0000704)).',
-  );
-  $response['member'][] = array(
-    '@id' => url($api_url . '/doc/', array('absolute' => TRUE)),
-    '@type' => 'Service',
-    'label' => 'API Documentation',
-    'description' => 'The WC3 Hydra compatible documentation for this API.',
-  );
-  $response['member'][] = array(
-    '@id' => url($api_url . '/vocab/', array('absolute' => TRUE)),
-    '@type' => 'Service',
-    'label' => 'Vocabulary',
-    'description' => 'Defines in-house locally defined vocabulary terms that ' .
-    'have been added specifically for this site.  These terms are typically ' .
-    'added because no other appropriate term exists in another community-vetted '.
-    'controlled vocabulary.',
-  );
-
-  $response['totalItems'] = count($response['member']);
-
-  $response['@context']['Collection'] = 'hydra:Collection';
-  $response['@context']['totalItems'] = 'hydra:totalItems';
-  $response['@context']['member'] = 'hydra:member';
-  $response['@context']['Service'] = 'dc:Service';
-  $response['@context']['label'] = 'rdfs:label';
-  $response['@context']['description'] = 'hydra:description';
-}
-
-/**
- * Implements hook_tripal_ws_value_alter().
- *
- * The hook_tripal_ws_value_alter is a hook created by the Tripal WS module.
- * It allows the modules to adjust the values of a field for display in
- * web services. This hook should be used sparingly. It is meant primarily
- * to adjust 3rd Party (non Tripal) fields so that they work with web
- * services.
- */
-function tripal_ws_tripal_ws_value_alter(&$items, $field, $instance) {
-  // The image module doesn't properly set the 'value' field, so we'll do it
-  // here.
-  if($field['type'] == 'image' and $field['module'] == 'image') {
-    foreach ($items as $delta => $details) {
-      if ($items[$delta] and array_key_exists('uri', $items[$delta])) {
-        $items[$delta]['value']['schema:url'] = file_create_url($items[$delta]['uri']);
-      }
-    }
-  }
-}

+ 5 - 3
tripal_ws/tripal_ws.module

@@ -21,11 +21,14 @@ function tripal_ws_init() {
   $version = 'v0.1';
   $api_url = $base_url . '/ws/' . $version;
 
+  $vocab = tripal_get_vocabulary_details('hydra');
+
   // Following the WC3 Hydra documentation, we want to add  LINK to the header
   // of the site that indicates where the API documentation can be found.
   // This allows a hydra-enabled client to discover the API and use it.
+  $url = preg_replace('/{accession}/', 'apiDocumenation', $vocab['urlprefix']);
   $attributes = array(
-    'rel' => 'http://www.w3.org/ns/hydra/core#apiDocumentation',
+    'rel' => $url,
     'href' => $api_url . '/ws-doc/',
   );
   drupal_add_html_head_link($attributes, $header = FALSE);
@@ -110,7 +113,6 @@ function tripal_ws_get_services() {
   global $base_url;
   $service_path = $base_url . '/web-services';
 
-
   // This should go out as ld+json
   drupal_add_http_header('Content-Type', 'application/ld+json');
 
@@ -119,7 +121,7 @@ function tripal_ws_get_services() {
   tripal_load_include_web_service_class('TripalVocabService_v0_1');
   $service = new TripalVocabService_v0_1($service_path);
   $vocab = tripal_get_vocabulary_details('hydra');
-  drupal_add_http_header('Link', '<' . $service->getServicePath() . '>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"');
+  drupal_add_http_header('Link', '<' . $service->getServicePath() . '>; rel="' . $vocab['url'] . '#apiDocumentation"');
   drupal_add_http_header('Cache-Control', "no-cache");
 
   try {