Package com.insightng.thirdparty.primal

Source Code of com.insightng.thirdparty.primal.PrimalClient

/*
* Copyright InsightNG (http://www.insightng.com/) 2012-2013
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of InsightNG nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.insightng.thirdparty.primal;

import static java.net.HttpURLConnection.HTTP_CREATED;
import static java.net.HttpURLConnection.HTTP_OK;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.httpclient.util.URIUtil;
import org.openrdf.model.Model;
import org.openrdf.model.Resource;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.LinkedHashModel;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.model.vocabulary.DC;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.SKOS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.stream.JsonReader;
import com.insightng.thirdparty.primal.exception.PrimalAuthenticationException;
import com.insightng.thirdparty.primal.exception.PrimalException;
import com.insightng.thirdparty.primal.vocabulary.PRIMAL;
import com.insightng.thirdparty.primal.vocabulary.PrimalTimePeriod;
import com.insightng.thirdparty.primal.vocabulary.ResponseKey;

/**
* PrimalClient provides full client query and manipulation access to the
* interest networks of a specified user on the Primal server. The Primal JSON
* results are processed and converted to RDF models (using the OpenRDF Sesame
* APIs).
*
* @see http://about.primal.com/documentation/
* @see http://www.openrdf.org/
* @author Jeen Broekstra
*/
public class PrimalClient {

  /* public constants */

  /**
   * enumeration of possible Primal API versions: {@link production} and
   * {@link latest}
   */
  public static enum Version {
    production, latest
  };

  /**
   * Maximum timeout for establishing a connection and for keeping it open, in seconds.
   */
  public static final int MAX_WAIT_TIME = 30;

  /**
   * The Primal data API URL (<code>https://data.primal.com/</code> ).
   */
  public static final String PRIMAL_DATA_API_URL = "https://data.primal.com/";

  public static final String PRIMAL_USER_API_URL = "https://api.primal.com/v1/users";

  /* private fields */

  private final PrimalAccount primalAccount;

  private final Logger logger = LoggerFactory.getLogger(this.getClass());

  private final static MultiThreadedHttpConnectionManager manager;
  private final HttpClient httpClient;

  private AuthScope dataApiAuthScope;

  private AuthScope userApiAuthScope;

  private final URL primalDataApi;

  private final URL primalUserApi;

  private Version version = Version.production;

  private static final ValueFactory vf = ValueFactoryImpl.getInstance();

  static {
    // Use class-shared MultiThreadedHttpConnectionManager to allow
    // concurrent access
    manager = new MultiThreadedHttpConnectionManager();

    // Allow 20 concurrent connections to the same host (default is 2)
    final HttpConnectionManagerParams params = new HttpConnectionManagerParams();
    params.setDefaultMaxConnectionsPerHost(20);
   
    // Connection timeout is the timeout to establish a connection.
    params.setConnectionTimeout((MAX_WAIT_TIME + 2) * 1000);
   
    // Socket Timeout is the timeout for an open connection to receive data.
    params.setSoTimeout((MAX_WAIT_TIME + 2) * 1000);
   
    manager.setParams(params);

    Runtime.getRuntime().addShutdownHook(new Thread("PrimalClient-shutdown") {
      public void run() {
        manager.closeIdleConnections(0);
        manager.shutdown();
      }
    });
  }

  /* constructors */

  /**
   * Creates a new Primal Client with the supplied {@link PrimalAccount}
   * credentials.
   */
  public PrimalClient(PrimalAccount primalAccount) {
    this(primalAccount, new HttpClient(manager));
  }

  /**
   * Constructor used for unit testing.
   *
   * @param primalAccount
   * @param httpClient
   */
  PrimalClient(PrimalAccount primalAccount, HttpClient httpClient) {
    this.primalAccount = primalAccount;
    try {
      primalDataApi = new URL(PRIMAL_DATA_API_URL);
      primalUserApi = new URL(PRIMAL_USER_API_URL);
    } catch (final MalformedURLException e) {
      throw new RuntimeException(e);
    }
    this.httpClient = httpClient;
    setPrimalUserAPICredentials();
  }

  /* public methods */

  /**
   * Creates a new content source.
   *
   * @param contentSourceName
   *            the name of the new content source
   * @param defaultContentSource
   *            flag to indicate if this content source should be considered
   *            the default source to operate on
   * @param contents
   *            specifies which content to include in this source, and
   *            optionally for each entry an instruction to only consider
   *            content published in that time period
   * @throws PrimalException
   *             if the content source could not be created.
   */
  public void createNewContentSource(String contentSourceName, boolean defaultContentSource,
      Map<URI, PrimalTimePeriod> contents) throws PrimalException {

    JsonObject newContentSource = new JsonObject();
    newContentSource.add("default", new JsonPrimitive(defaultContentSource));

    JsonObject contentsObject = new JsonObject();
    for (Entry<URI, PrimalTimePeriod> entry : contents.entrySet()) {

      JsonObject timePeriodValue = new JsonObject();
      if (entry.getValue() != null) {
        timePeriodValue.add("time", new JsonPrimitive(entry.getValue().toString()));
      }

      contentsObject.add(entry.getKey().stringValue(), timePeriodValue);
    }

    newContentSource.add("contents", contentsObject);

    JsonObject payload = new JsonObject();
    payload.add(contentSourceName, newContentSource);

    PostMethod method = new PostMethod(PRIMAL_DATA_API_URL + "/@Sources");

    try {
      configureRequest(method);
      logRequestDetails(method, PRIMAL_DATA_API_URL + "/@Sources");
      ByteArrayRequestEntity requestEntity = new ByteArrayRequestEntity(payload.toString().getBytes(
          "UTF-8"), "application/json");

      method.setRequestEntity(requestEntity);

      final int httpCode = httpClient.executeMethod(method);
      logger.debug("http response code:" + httpCode);
      if (HTTP_CREATED != httpCode) {
        if (HTTP_UNAUTHORIZED == httpCode) {
          throw new PrimalAuthenticationException();
        } else {
          throw new PrimalException("error executing content source creation request", httpCode);
        }
      }
    } catch (final HttpException e) {
      logger.warn("error executing content source creation request", e);
      throw new PrimalException("error executing content source creation request", 0, e);
    } catch (final IOException e) {
      logger.warn("error executing content source creation request", e);
      throw new PrimalException("error executing content source creation request", 0, e);
    } finally {
      method.releaseConnection();
    }
  }

  public List<URI> getCustomContentSources() throws PrimalException {

    final String requestURL = PRIMAL_DATA_API_URL + "@Sources";

    final GetMethod method = new GetMethod(requestURL);
    try {
      configureRequest(method);
      logRequestDetails(method, requestURL);

      final int httpCode = httpClient.executeMethod(method);
      logger.debug("response code: " + httpCode);
      if (httpCode == HTTP_OK) {
        final JsonReader reader = new JsonReader(new InputStreamReader(
            method.getResponseBodyAsStream()));

        final JsonParser jsonParser = new JsonParser();
        final JsonObject responseObject = jsonParser.parse(reader).getAsJsonObject();
       
        List<URI> sources = new ArrayList<URI>();
        for (Entry<String, JsonElement> entry: responseObject.entrySet()) {
          sources.add(vf.createURI(entry.getKey()));
        }
        return sources;
      }
      else if (HTTP_UNAUTHORIZED == httpCode) {
        throw new PrimalAuthenticationException();
      } else {
        throw new PrimalException("error retrieving content sources", httpCode);
      }

    } catch (final HttpException e) {
      logger.warn("error retrieving content sources", e);
      throw new PrimalException("error retrieving content sources", 0, e);
    } catch (final IOException e) {
      logger.warn("error retrieving content sources", e);
      throw new PrimalException("error retrieving content sources", 0, e);
    } finally {
      method.releaseConnection();
    }
  }

  public Model getResult(String topic) throws PrimalException {
    return getResult(topic, null);
  }

  /**
   * Returns an RDF model describing the supplied interest network. The model
   * will contain concepts that have been explicitly provided by the user, as
   * well as synthesized concepts that Primal has generated and inserted into
   * the interest network. The concepts within the response model will be
   * validated with content taken from the website(s) defined within {source}.
   *
   * @param topic
   *            Specification of the core topic of interest for this search.
   *            Values should be provided in a forward slash and/or semi-colon
   *            delimited format, where slashes delimit a conceptual
   *            hierarchy, and semi-colons provide a breadth of conceptual
   *            detail at the same hierarchical level. For example:
   *            <ul>
   *            <li><code>tidal+research</code><br>
   *            Implies a simple use case of interest in the field of tidal
   *            research.</li>
   *            <li><code>baking/cookies</code><br>
   *            Implies interest in cookies of the edible variety rather than
   *            the browser data storage variety.</li>
   *            <li><code>shelter/caves;houses;tents</code><br>
   *            Implies interest in the concepts of caves, houses and tents as
   *            being types of shelters.</li>
   *            </ul>
   * @param contentSource
   *            source of content.
   * @param storage
   *            name of the interest network. Valid characters for interest
   *            network names include: A-Z, a-z, 0-9, underscores and spaces.
   *
   * @return a {@link Model} containing RDF statements representing a
   *         description of the interest network. Concepts are represented as
   *         instances of the class <code>skos:Concept</code> (
   *         {@link SKOS.CONCEPT}) having a <code>skos:prefLabel</code> (
   *         {@link SKOS.PREF_LABEL}) property with their human-readable
   *         label. Content is represented as instances of the class
   *         <code>primal:ContentItem</code> ({@link PRIMAL.CONTENT_ITEM}).
   * @throws PrimalException
   *             if an error occurred in communicating with the server.
   */
  public Model getResult(String topic, String contentSource) throws PrimalException {
    return getResult(topic, contentSource, 0, 0, 0);
  }

  public Model getResult(String topic, int wait) throws PrimalException {
    return getResult(topic, null, wait);
  }

  /**
   * Returns an RDF model describing the supplied interest network. The model
   * will contain concepts that have been explicitly provided by the user, as
   * well as synthesized concepts that Primal has generated and inserted into
   * the interest network. The concepts within the response model will be
   * validated with content taken from the website(s) defined within {source}.
   *
   * @param topic
   *            Specification of the core topic of interest for this search.
   *            Values should be provided in a forward slash and/or semi-colon
   *            delimited format, where slashes delimit a conceptual
   *            hierarchy, and semi-colons provide a breadth of conceptual
   *            detail at the same hierarchical level. For example:
   *            <ul>
   *            <li><code>tidal+research</code><br>
   *            Implies a simple use case of interest in the field of tidal
   *            research.</li>
   *            <li><code>baking/cookies</code><br>
   *            Implies interest in cookies of the edible variety rather than
   *            the browser data storage variety.</li>
   *            <li><code>shelter/caves;houses;tents</code><br>
   *            Implies interest in the concepts of caves, houses and tents as
   *            being types of shelters.</li>
   *            </ul>
   * @param contentSource
   *            source of content.
   * @param wait
   *            Instruct Primal to wait for a specified period of time (in
   *            seconds) until a request reaches a specified state before
   *            returning a response. Can only be used in conjunction with a
   *            specific status parameter.
   * @param storage
   *            name of the interest network. Valid characters for interest
   *            network names include: A-Z, a-z, 0-9, underscores and spaces.
   * @param status
   *            Instruct Primal to return a response if and only if the
   *            workflow is currently in the specified state. May be null, in
   *            which case the workflow state will not be considered.
   *
   * @return a {@link Model} containing RDF statements representing a
   *         description of the interest network. Concepts are represented as
   *         instances of the class <code>skos:Concept</code> (
   *         {@link SKOS.CONCEPT}) having a <code>skos:prefLabel</code> (
   *         {@link SKOS.PREF_LABEL}) property with their human-readable
   *         label. Content is represented as instances of the class
   *         <code>primal:ContentItem</code> ({@link PRIMAL.CONTENT_ITEM}).
   * @throws PrimalException
   *             if an error occurred in communicating with the server.
   */
  public Model getResult(String topic, String contentSource, int wait) throws PrimalException {
    return getResult(topic, contentSource, 0, 0, wait);
  }

  public Model getResult(String topic, float minScore, int maxContentCount, int wait)
      throws PrimalException {
    return getResult(topic, null, minScore, maxContentCount, wait);
  }

  /**
   * Returns an RDF model describing the supplied interest network. The model
   * will contain concepts that have been explicitly provided by the user, as
   * well as synthesized concepts that Primal has generated and inserted into
   * the interest network. The concepts within the response model will be
   * validated with content taken from the website(s) defined within {source}.
   *
   * @param topic
   *            Specification of the core topic of interest for this search.
   *            Values should be provided in a forward slash and/or semi-colon
   *            delimited format, where slashes delimit a conceptual
   *            hierarchy, and semi-colons provide a breadth of conceptual
   *            detail at the same hierarchical level. For example:
   *            <ul>
   *            <li><code>tidal+research</code><br>
   *            Implies a simple use case of interest in the field of tidal
   *            research.</li>
   *            <li><code>baking/cookies</code><br>
   *            Implies interest in cookies of the edible variety rather than
   *            the browser data storage variety.</li>
   *            <li><code>shelter/caves;houses;tents</code><br>
   *            Implies interest in the concepts of caves, houses and tents as
   *            being types of shelters.</li>
   *            </ul>
   * @param contentSource
   *            source of content.
   * @param minScore
   *            modifies the results returned based on relevancy by changing
   *            the minimum acceptable primal:HasScore value. If set to zero,
   *            all results will be returned.
   * @param maxContentCount
   *            modifies the number of results returned by changing the
   *            maximum number of content items to return. For example, you
   *            would use this parameter to return a "top ten" list of content
   *            items. If set to zero, all result content items will be
   *            returned.
   * @param wait
   *            Instruct Primal to wait for a specified period of time (in
   *            seconds) until a request reaches a specified state before
   *            returning a response. Can only be used in conjunction with a
   *            specific status parameter.
   * @param storage
   *            name of the interest network. Valid characters for interest
   *            network names include: A-Z, a-z, 0-9, underscores and spaces.
   * @param status
   *            Instruct Primal to return a response if and only if the
   *            workflow is currently in the specified state. May be null, in
   *            which case the workflow state will not be considered.
   *
   * @return a {@link org.openrdf.model.Model} containing RDF statements
   *         representing a description of the interest network. Concepts are
   *         represented as instances of the class <code>skos:Concept</code> (
   *         {@link SKOS.CONCEPT}) having a <code>skos:prefLabel</code> (
   *         {@link SKOS.PREF_LABEL}) property with their human-readable
   *         label. Content is represented as instances of the class
   *         <code>primal:ContentItem</code> ({@link PRIMAL.CONTENT_ITEM}).
   * @throws PrimalException
   *             if an error occurred in communicating with the server.
   */
  public Model getResult(String topic, String contentSource, float minScore, int maxContentCount, int wait)
      throws PrimalException {

    if (wait > MAX_WAIT_TIME) {
      throw new PrimalException("wait time is not allowed to exceed " + MAX_WAIT_TIME + " seconds", 0);
    }

    final Model model = new LinkedHashModel();

    final String requestURL = createRequestURLString(topic, contentSource, minScore, maxContentCount,
        wait);

    final GetMethod method = new GetMethod(requestURL);
    try {
      configureRequest(method);
      logRequestDetails(method, requestURL);

      final int httpCode = httpClient.executeMethod(method);
      logger.debug("response code: " + httpCode);
      if (httpCode == HTTP_OK) {
        final JsonReader reader = new JsonReader(new InputStreamReader(
            method.getResponseBodyAsStream()));

        final JsonParser jsonParser = new JsonParser();
        final JsonObject responseObject = jsonParser.parse(reader).getAsJsonObject();

        logger.trace("json response string: " + responseObject.toString());

        final JsonObject responseInfo = responseObject
            .getAsJsonObject(ResponseKey.PRIMAL_RESPONSE_INFO);

        if (responseInfo != null) {
          logger.debug("processing primal response info in response");

          final Resource responseInfoId = vf.createURI(requestURL);

          ResponseValues.processIntValue(responseInfoId, PRIMAL.CONTENT_COUNT, model,
              PRIMAL.RESPONSE_INFO, responseInfo, ResponseKey.PRIMAL_CONTENT_COUNT);

          ResponseValues.processIntValue(responseInfoId, PRIMAL.TOTAL_CONCEPTS_COUNT, model,
              PRIMAL.RESPONSE_INFO, responseInfo, ResponseKey.PRIMAL_TOTAL_CONCEPTS_COUNT);

          ResponseValues.processFloatValue(responseInfoId, PRIMAL.MIN_SEMANTIC_COVERAGE, model,
              PRIMAL.RESPONSE_INFO, responseInfo, ResponseKey.PRIMAL_MIN_SEMANTIC_COVERAGE);

          ResponseValues.processStringValue(responseInfoId, PRIMAL.STATUS, model,
              PRIMAL.RESPONSE_INFO, responseInfo,
              Version.latest == getPrimalVersion() ? ResponseKey.PRIMAL_STATUS
                  : ResponseKey.PRIMAL_STATUS);

          ResponseValues.processIntValue(responseInfoId, PRIMAL.CONTENT_FILTERED_OUT, model,
              PRIMAL.RESPONSE_INFO, responseInfo, ResponseKey.PRIMAL_CONTENT_FILTERED_OUT);

          ResponseValues.processIntValue(responseInfoId, PRIMAL.CONCEPT_COUNT, model,
              PRIMAL.RESPONSE_INFO, responseInfo, ResponseKey.PRIMAL_CONCEPT_COUNT);

          ResponseValues.processFloatValue(responseInfoId, PRIMAL.HIGH_CONTENT_SCORE, model,
              PRIMAL.RESPONSE_INFO, responseInfo, ResponseKey.PRIMAL_HIGH_CONTENT_SCORE);

          ResponseValues.processFloatValue(responseInfoId, PRIMAL.TERM_COVERAGE, model,
              PRIMAL.RESPONSE_INFO, responseInfo, ResponseKey.PRIMAL_TERM_COVERAGE);

          if (responseInfo.has(ResponseKey.PRIMAL_RECOGNIZED_TERMS)) {
            final JsonArray recTerms = responseInfo.get(ResponseKey.PRIMAL_RECOGNIZED_TERMS)
                .getAsJsonArray();

            for (int i = 0; i < recTerms.size(); i++) {
              final String term = recTerms.get(i).getAsString();
              model.add(responseInfoId, PRIMAL.RECOGNIZED_TERM, vf.createLiteral(term),
                  PRIMAL.RESPONSE_INFO);
            }
          }

          ResponseValues.processStringValue(responseInfoId, PRIMAL.VIABILITY_MESSAGE, model,
              PRIMAL.RESPONSE_INFO, responseInfo, ResponseKey.PRIMAL_VIABILITY_MESSAGE);

          ResponseValues.processFloatValue(responseInfoId, PRIMAL.SEMANTIC_RATIO, model,
              PRIMAL.RESPONSE_INFO, responseInfo, ResponseKey.PRIMAL_SEMANTIC_RATIO);

          ResponseValues.processBooleanValue(responseInfoId, PRIMAL.HAS_EXPANSION, model,
              PRIMAL.RESPONSE_INFO, responseInfo, ResponseKey.PRIMAL_HAS_EXPANSION);

        }

        logger.debug("processing SKOS concepts in response");

        final JsonObject conceptScheme = responseObject
            .getAsJsonObject(ResponseKey.SKOS_CONCEPT_SCHEME);

        final JsonElement topConcepts = conceptScheme.get(ResponseKey.SKOS_HAS_TOP_CONCEPT);

        final JsonObject collection = conceptScheme.getAsJsonObject(ResponseKey.SKOS_COLLECTION);

        final Set<Entry<String, JsonElement>> members = collection.entrySet();

        final Resource conceptSchemeId = vf.createBNode();
        model.add(conceptSchemeId, RDF.TYPE, SKOS.CONCEPT_SCHEME, PRIMAL.CONCEPTS);

        final Resource collectionId = vf.createBNode();
        model.add(collectionId, RDF.TYPE, SKOS.COLLECTION, PRIMAL.CONCEPTS);

        final List<URI> rootConcepts = new ArrayList<URI>();

        if (topConcepts.isJsonArray()) {
          URI rcURI;
          final JsonArray rcArray = topConcepts.getAsJsonArray();
          for (int i = 0; i < rcArray.size(); i++) {
            rcURI = vf.createURI(rcArray.get(i).getAsString());
            rootConcepts.add(rcURI);
          }
        } else {
          final URI rcURI = vf.createURI(topConcepts.getAsString());
          rootConcepts.add(rcURI);
        }

        for (final URI rootConceptId : rootConcepts) {
          model.add(collectionId, SKOS.MEMBER, rootConceptId, PRIMAL.CONCEPTS);
          model.add(conceptSchemeId, SKOS.HAS_TOP_CONCEPT, rootConceptId, PRIMAL.CONCEPTS);

          for (final Entry<String, JsonElement> member : members) {
            final String conceptId = member.getKey();

            final JsonObject conceptAsJson = member.getValue().getAsJsonObject();

            final JsonElement prefLabelElement = conceptAsJson.get(ResponseKey.SKOS_PREF_LABEL);
            String prefLabel = null;
            if (prefLabelElement != null && !prefLabelElement.isJsonNull()) {
              prefLabel = prefLabelElement.getAsString();
            } else {
              logger.warn("no prefLabel found for {}. Skipping concept creation", conceptId);
              continue;
            }

            final URI concept = vf.createURI(conceptId);
            model.add(concept, RDF.TYPE, SKOS.CONCEPT, PRIMAL.CONCEPTS);
            model.add(concept, SKOS.PREF_LABEL, vf.createLiteral(prefLabel), PRIMAL.CONCEPTS);

            model.add(collectionId, SKOS.MEMBER, concept, PRIMAL.CONCEPTS);

            ResponseValues.processFloatValue(concept, PRIMAL.CONCEPT_SCORE, model,
                PRIMAL.CONCEPTS, conceptAsJson, ResponseKey.PRIMAL_CONCEPT_SCORE);

            ResponseValues.processStringValue(concept, SKOS.ALT_LABEL, model, PRIMAL.CONCEPTS,
                conceptAsJson, ResponseKey.SKOS_ALT_LABEL);

            ResponseValues.processStringValue(concept, PRIMAL.SOURCE, model, PRIMAL.CONCEPTS,
                conceptAsJson, ResponseKey.PRIMAL_SOURCE);

            final JsonElement narrowerElem = conceptAsJson.get(ResponseKey.SKOS_NARROWER);

            if (narrowerElem != null) {
              final JsonArray narrower = narrowerElem.getAsJsonArray();

              for (int i = 0; i < narrower.size(); i++) {
                final String narrowerId = narrower.get(i).getAsString();
                model.add(concept, SKOS.NARROWER, vf.createURI(narrowerId), PRIMAL.CONCEPTS);
              }
            }
          }
        }

        logger.debug("processing DC part of response...");
        final JsonElement dcCollection = responseObject.get(ResponseKey.DC_COLLECTION);

        if (dcCollection.isJsonArray()) {
          final JsonArray array = dcCollection.getAsJsonArray();

          logger.debug("response contains {} content items", array.size());

          for (int i = 0; i < array.size(); i++) {
            final JsonObject contentItem = array.get(i).getAsJsonObject();

            final String dcIdentifier = contentItem.get(ResponseKey.DC_IDENTIFIER).getAsString();

            final URI contentItemId = vf.createURI(dcIdentifier);
            model.add(contentItemId, RDF.TYPE, PRIMAL.CONTENT_ITEM, PRIMAL.CONTENT);

            model.add(contentItemId, DC.IDENTIFIER, vf.createLiteral(dcIdentifier),
                PRIMAL.CONTENT);

            ResponseValues.processStringValue(contentItemId, DC.TITLE, model, PRIMAL.CONTENT,
                contentItem, ResponseKey.DC_TITLE);

            ResponseValues.processStringValue(contentItemId, DC.DESCRIPTION, model,
                PRIMAL.CONTENT, contentItem, ResponseKey.DC_DESCRIPTION);

            ResponseValues.processStringValue(contentItemId, DC.PUBLISHER, model, PRIMAL.CONTENT,
                contentItem, ResponseKey.DC_PUBLISHER);

            ResponseValues.processStringValue(contentItemId, DC.SOURCE, model, PRIMAL.CONTENT,
                contentItem, ResponseKey.DC_SOURCE);

            ResponseValues.processStringValue(contentItemId, DC.RELATION, model, PRIMAL.CONTENT,
                contentItem, ResponseKey.DC_RELATION);

            ResponseValues.processDateValue(contentItemId, DC.DATE, model, PRIMAL.CONTENT,
                contentItem, ResponseKey.DC_DATE);

            ResponseValues.processFloatValue(contentItemId, PRIMAL.CONTENT_SCORE, model,
                PRIMAL.CONTENT, contentItem, ResponseKey.PRIMAL_CONTENT_SCORE);

            final JsonArray subjects = contentItem.get(ResponseKey.DC_SUBJECT).getAsJsonArray();
            for (int j = 0; j < subjects.size(); j++) {
              final String subject = subjects.get(j).getAsString();
              model.add(contentItemId, DC.SUBJECT, vf.createURI(subject), PRIMAL.CONTENT);
            }
          }

        } else {
          // TODO can this happen?
          logger.warn("json element " + ResponseKey.DC_COLLECTION
              + " of unexpected type. Expected array.");
        }
        logger.debug("response processing complete");
      } else {
        logger.warn("response not ok " + httpCode);
        if (HTTP_UNAUTHORIZED == httpCode) {
          throw new PrimalAuthenticationException();
        } else {
          throw new PrimalException("error retrieving results.", httpCode);
        }
      }
    } catch (final HttpException e) {
      logger.warn("protocol error retrieving result", e);
      throw new PrimalException("protocol error retrieving result", 0, e);
    } catch (final IOException e) {
      logger.warn("I/O error retrieving result", e);
      throw new PrimalException("I/O error retrieving result", 0, e);
    } finally {
      method.releaseConnection();
    }

    return model;
  }

  public void addConcept(String topic, boolean expand) throws PrimalException {
    addConcept(topic, null, expand);
  }

  /**
   * Creates a topic.
   *
   * @param topic
   *            Specification of the core topic of interest for this add
   *            request. Values should be provided in a forward slash and/or
   *            semi-colon delimited format, where slashes delimit a
   *            conceptual hierarchy, and semi-colons provide a breadth of
   *            conceptual detail at the same hierarchical level. For example:
   *            <ul>
   *            <li><code>tidal+research</code><br>
   *            Implies a simple use case of interest in the field of tidal
   *            research.</li>
   *            <li><code>baking/cookies</code><br>
   *            Implies interest in cookies of the edible variety rather than
   *            the browser data storage variety.</li>
   *            <li><code>shelter/caves;houses;tents</code><br>
   *            Implies interest in the concepts of caves, houses and tents as
   *            being types of shelters.</li>
   *            </ul>
   * @param contentSource
   *            source of content. If set to null, the default content
   *            source(s) will be used.
   * @param expand
   *            Indicates if Primal should initiate semantic expansion on the
   *            submitted concept.
   * @param storage
   *            name of the interest network. Valid characters for interest
   *            network names include: A-Z, a-z, 0-9, underscores and spaces.
   *
   * @throws PrimalException
   *             if an error occurred while adding the concept.
   */
  public void addConcept(String topic, String contentSource, boolean expand) throws PrimalException {
    final String requestURL = createRequestURLString(topic, contentSource, 0, 0, 0);

    String methodName = "POST";
    HttpMethod method = null;
    if (expand) {
      method = new PostMethod(requestURL);

    } else {
      method = new PutMethod(requestURL);
      methodName = "PUT";
    }
    try {
      configureRequest(method);
      logRequestDetails(method, requestURL);

      final int httpCode = httpClient.executeMethod(method);
      logger.debug("http response code:" + httpCode);
      if (HTTP_CREATED != httpCode) {
        if (HTTP_UNAUTHORIZED == httpCode) {
          throw new PrimalAuthenticationException();
        } else {
          throw new PrimalException("error executing add request", httpCode);
        }
      }
    } catch (final HttpException e) {
      logger.warn("error executing add request", e);
      throw new PrimalException("error executing add request", 0, e);
    } catch (final IOException e) {
      logger.warn("error executing add request", e);
      throw new PrimalException("error executing add request", 0, e);
    } finally {
      method.releaseConnection();
    }
  }

  public void deleteConcept(String topicOfInterest) throws PrimalException {
    deleteConcept(topicOfInterest, null);
  }

  /**
   * Removes a concept from an interest network.
   *
   * @param topic
   *            Specification of the core topic of interest for this delete
   *            request. Values should be provided in a forward slash and/or
   *            semi-colon delimited format, where slashes delimit a
   *            conceptual hierarchy, and semi-colons provide a breadth of
   *            conceptual detail at the same hierarchical level. For example:
   *            <ul>
   *            <li><code>tidal+research</code><br>
   *            Implies a simple use case of interest in the field of tidal
   *            research.</li>
   *            <li><code>baking/cookies</code><br>
   *            Implies interest in cookies of the edible variety rather than
   *            the browser data storage variety.</li>
   *            <li><code>shelter/caves;houses;tents</code><br>
   *            Implies interest in the concepts of caves, houses and tents as
   *            being types of shelters.</li>
   *            </ul>
   * @param contentSource
   *            source of content.
   * @param storage
   *            name of the interest network. Valid characters for interest
   *            network names include: A-Z, a-z, 0-9, underscores and spaces.
   *
   * @throws PrimalException
   *             if an error occurred while removing the concept.
   */
  public void deleteConcept(String topic, String contentSource) throws PrimalException {
    final String requestURL = createRequestURLString(topic, contentSource, 0, 0, 0);

    HttpMethod method = new DeleteMethod(requestURL);
    try {
      configureRequest(method);
      logRequestDetails(method, requestURL);

      final int httpCode = httpClient.executeMethod(method);
      logger.debug("http response code:" + httpCode);
      if (HTTP_OK != httpCode) {
        if (HTTP_UNAUTHORIZED == httpCode) {
          throw new PrimalAuthenticationException();
        } else {
          throw new PrimalException("error executing delete request.", httpCode);
        }
      }
    } catch (final HttpException e) {
      logger.warn("error executing delete request", e);
      throw new PrimalException("error executing delete request", 0, e);
    } catch (final IOException e) {
      logger.warn("error executing delete request", e);
      throw new PrimalException("error executing delete request", 0, e);
    } finally {
      method.releaseConnection();
    }
  }

  /**
   * retrieves the API version this PrimalClient currently operates on.
   *
   * @return the current version.
   */
  public Version getPrimalVersion() {
    return version;
  }

  /**
   * set the API version this PrimalClient will operate on.
   *
   * @param version
   *            the version.
   */
  public void setPrimalVersion(Version version) {
    this.version = version;
  }

  /**
   * Shut down this client, freeing up resources.
   */
  public void shutdown() {
    httpClient.getHttpConnectionManager().closeIdleConnections(0);
  }

  /**
   * deletes user with the given username via the Primal User Management API
   *
   * @see https://about.primal.com/developers/documentation/user-management
   * @param username
   *            the username to delete
   * @throws PrimalException
   *             if the user could not be deleted.
   */
  public void deleteUser(String username) throws PrimalException {
    if (username == null) {
      throw new PrimalException("username can not be null", 0);
    }

    String requestURL = null;
    try {
      requestURL = PRIMAL_USER_API_URL + "/" + URIUtil.encodePath(username, "UTF-8");
    } catch (URIException e1) {
      throw new RuntimeException(e1);
    }
    DeleteMethod method = new DeleteMethod(requestURL);
    try {
      configureRequest(method);
      logRequestDetails(method, requestURL);

      final int httpCode = httpClient.executeMethod(method);
      logger.debug("http response code:" + httpCode);
      if (HTTP_OK != httpCode) {
        if (HTTP_UNAUTHORIZED == httpCode) {
          throw new PrimalAuthenticationException();
        } else {
          logger.debug(method.getResponseBodyAsString());
          throw new PrimalException("error executing deleteUSer request", httpCode);
        }
      }
    } catch (final HttpException e) {
      logger.warn("error executing delete request", e);
      throw new PrimalException("error executing delete request", 0, e);
    } catch (final IOException e) {
      logger.warn("error executing delete request", e);
      throw new PrimalException("error executing delete request", 0, e);
    } finally {
      method.releaseConnection();
    }

  }

  /**
   * Creates a new user with the given username/password via the Primal User
   * Management API
   *
   * @see https://about.primal.com/developers/documentation/user-management
   * @param username
   *            the username to create
   * @param password
   *            the paswsword to assign the new username
   * @return the full username as created by Primal
   * @throws PrimalException
   *             if the user could not be created.
   */
  public String createUser(String username, String password) throws PrimalException {
    PostMethod method = new PostMethod(PRIMAL_USER_API_URL);

    String createdUser = null;

    final JsonObject payload = new JsonObject();
    payload.add("name", new JsonPrimitive(username));
    payload.add("password", new JsonPrimitive(password));

    try {
      ByteArrayRequestEntity requestEntity = new ByteArrayRequestEntity(payload.toString().getBytes(
          "UTF-8"), "application/json");
      method.setRequestEntity(requestEntity);
    } catch (UnsupportedEncodingException e1) {
      throw new RuntimeException(e1);
    }

    try {
      configureRequest(method);
      logRequestDetails(method, PRIMAL_USER_API_URL);

      final int httpCode = httpClient.executeMethod(method);
      logger.debug("http response code:" + httpCode);
      if (HTTP_CREATED != httpCode) {
        if (HTTP_UNAUTHORIZED == httpCode) {
          throw new PrimalAuthenticationException();
        } else {
          logger.debug(method.getResponseBodyAsString());
          throw new PrimalException("error executing createUser request", httpCode);
        }
      } else {
        final JsonReader reader = new JsonReader(new InputStreamReader(
            method.getResponseBodyAsStream()));

        final JsonParser jsonParser = new JsonParser();
        final JsonObject responseObject = jsonParser.parse(reader).getAsJsonObject();

        createdUser = responseObject.get("name").getAsString();

      }
    } catch (final HttpException e) {
      logger.warn("error executing add request", e);
      throw new PrimalException("error executing add request", 0, e);
    } catch (final IOException e) {
      logger.warn("error executing add request", e);
      throw new PrimalException("error executing add request", 0, e);
    } finally {
      method.releaseConnection();
    }

    return createdUser;
  }

  /**
   * Changes the password for the given user via the Primal User Management
   * API
   *
   * @see https://about.primal.com/developers/documentation/user-management
   * @param username
   *            the username for which to change the password
   * @param password
   *            the paswsword to assign
   * @return the full username as created by Primal
   * @throws PrimalException
   *             if the user could not be created.
   */
  public void changePassword(String username, String password) throws PrimalException {
    if (username == null) {
      throw new PrimalException("username can not be null", 0);
    }
    if (password == null) {
      throw new PrimalException("password can not be null", 0);
    }

    String requestURL = null;
    try {
      requestURL = PRIMAL_USER_API_URL + "/" + URIUtil.encodePath(username, "UTF-8");
    } catch (URIException e2) {
      throw new RuntimeException(e2);
    }

    PostMethod method = new PostMethod(requestURL);

    final JsonObject payload = new JsonObject();
    payload.add("password", new JsonPrimitive(password));

    try {
      ByteArrayRequestEntity requestEntity = new ByteArrayRequestEntity(payload.toString().getBytes(
          "UTF-8"), "application/json");
      method.setRequestEntity(requestEntity);
    } catch (UnsupportedEncodingException e1) {
      throw new RuntimeException(e1);
    }

    try {
      configureRequest(method);
      logRequestDetails(method, requestURL);

      final int httpCode = httpClient.executeMethod(method);
      logger.debug("http response code:" + httpCode);
      if (HTTP_OK != httpCode) {
        if (HTTP_UNAUTHORIZED == httpCode) {
          throw new PrimalAuthenticationException();
        } else {
          logger.debug(method.getResponseBodyAsString());
          throw new PrimalException("error executing change password request", httpCode);
        }
      }
    } catch (final HttpException e) {
      logger.warn("error executing change request", e);
      throw new PrimalException("error executing change request", 0, e);
    } catch (final IOException e) {
      logger.warn("error executing change request", e);
      throw new PrimalException("error executing change request", 0, e);
    } finally {
      method.releaseConnection();
    }

  }

  /**
   * Retrieve a list of users via the Primal User API
   *
   * @see https://about.primal.com/developers/documentation/user-management
   *
   * @return a list of usernames.
   *
   * @throws PrimalException
   */
  public List<String> getUsers() throws PrimalException {
    GetMethod method = new GetMethod(PRIMAL_USER_API_URL);
    try {
      configureRequest(method);
      logRequestDetails(method, PRIMAL_USER_API_URL);
      final int httpCode = httpClient.executeMethod(method);
      if (HTTP_OK != httpCode) {
        if (HTTP_UNAUTHORIZED == httpCode) {
          throw new PrimalAuthenticationException();
        } else {
          logger.error("error retrieving users: " +  method.getResponseBodyAsString());
          throw new PrimalException("error retrieving users", httpCode);
        }
      } else {
        final JsonReader reader = new JsonReader(new InputStreamReader(
            method.getResponseBodyAsStream()));

        final JsonParser jsonParser = new JsonParser();
        final JsonObject response = jsonParser.parse(reader).getAsJsonObject();

        final JsonArray users = response.getAsJsonArray("users");
       
        List<String> result = new ArrayList<String>();
        for (int i = 0; i < users.size(); i++) {
          String user = users.get(i).getAsJsonObject().get("name").getAsString();
          result.add(user);
        }
        return result;
      }

    } catch (IOException e) {
      throw new PrimalException("error retrieving list of users", 0, e);
    } finally {
      method.releaseConnection();
    }
  }

  /**
   * Set the username and password for authentication with the Primal Data
   * API.
   */
  public void setPrimalDataAPICredentials(String username, String password) {
    if (username != null && password != null) {
      logger.debug("Setting username '{}' and password for server at {}.", username,
          primalDataApi.toExternalForm());
      dataApiAuthScope = new AuthScope(primalDataApi.getHost(), AuthScope.ANY_PORT);
      httpClient.getState().setCredentials(dataApiAuthScope,
          new UsernamePasswordCredentials(username, password));
      httpClient.getParams().setAuthenticationPreemptive(true);
    } else {
      logger.warn("incomplete credentials supplied, not using authentication");
      dataApiAuthScope = null;
      httpClient.getState().clearCredentials();
      httpClient.getParams().setAuthenticationPreemptive(false);
    }
  }

  /* protected/private methods */

  /**
   * Set the username and password for authentication with the Primal User
   * API.
   */
  private void setPrimalUserAPICredentials() {
    if (primalAccount.getUsername() != null && primalAccount.getPassword() != null) {
      logger.debug("Setting username '{}' and password for server at {}.", primalAccount.getUsername(),
          primalUserApi.toExternalForm());
      userApiAuthScope = new AuthScope(primalUserApi.getHost(), AuthScope.ANY_PORT);
      httpClient.getState()
          .setCredentials(
              userApiAuthScope,
              new UsernamePasswordCredentials(primalAccount.getUsername(), primalAccount
                  .getPassword()));
      httpClient.getParams().setAuthenticationPreemptive(true);
    } else {
      logger.warn("incomplete credentials supplied, not using authentication");
      userApiAuthScope = null;
      httpClient.getState().clearCredentials();
      httpClient.getParams().setAuthenticationPreemptive(false);
    }
  }

  private final void configureRequest(HttpMethod method) {
    if (dataApiAuthScope != null && httpClient.getState().getCredentials(dataApiAuthScope) != null) {
      method.setDoAuthentication(true);
    } else {
      method.setDoAuthentication(false);
    }

    if (primalAccount.getApplicationID() != null) {
      method.setRequestHeader("Primal-App-ID", primalAccount.getApplicationID());
    }
    if (primalAccount.getApplicationKey() != null) {
      method.setRequestHeader("Primal-App-Key", primalAccount.getApplicationKey());
    }

    method.setRequestHeader("Primal-Version", getPrimalVersion().toString());
  }

  private String createRequestURLString(String topic, String contentSource, float minScore,
      int maxContentCount, int wait) {
    String url = primalDataApi.toExternalForm();

    try {

      if (topic != null) {
        url += URIUtil.encodePath(topic);
      }
      if (minScore > 0 || maxContentCount > 0 || contentSource != null || wait > 0) {
        url += "?";
        if (minScore > 0) {
          url += "primal:contentScore:min=" + minScore;
          url += "&";
        }
        if (maxContentCount > 0) {
          url += "primal:contentCount:max=" + maxContentCount;
          url += "&";
        }
        if (contentSource != null) {
          url += "contentSource=" + URIUtil.encodePath(contentSource);
          url += "&";
        }
        if (wait > 0) {
          url += "timeOut=" + wait;
          url += "&";
        }
      }
    } catch (final URIException e) {
      // can only happen if the default protocol charset is not supported.
      // Since we use UTF-8 consistently, that
      // should never happen.
      throw new RuntimeException(e);
    }

    logger.debug("request url: " + url);
    return url;
  }

  private void logRequestDetails(HttpMethod method, String requestURL) {
    logger.debug("{} request", method.getName());
    logger.debug("\trequest URL: {}", requestURL);
    for (Header header : method.getRequestHeaders()) {
      logger.debug("\t{}: {}", header.getName(), header.getValue());
    }
  }

}
TOP

Related Classes of com.insightng.thirdparty.primal.PrimalClient

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.