All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gedcomx.rs.client.GedcomxApplicationState Maven / Gradle / Ivy

There is a newer version: 2.180.0
Show newest version
/**
 * Copyright Intellectual Reserve, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.gedcomx.rs.client;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientRequest;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.core.header.LinkHeader;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHeaders;
import org.gedcomx.Gedcomx;
import org.gedcomx.atom.AtomModel;
import org.gedcomx.common.Attributable;
import org.gedcomx.common.Attribution;
import org.gedcomx.common.ResourceReference;
import org.gedcomx.links.Link;
import org.gedcomx.links.SupportsLinks;
import org.gedcomx.rs.client.util.EmbeddedLinkLoader;
import org.gedcomx.rs.client.util.HttpWarning;
import org.gedcomx.rt.GedcomxConstants;
import org.gedcomx.rt.Rel;
import org.gedcomx.source.SourceDescription;

import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.*;

/**
 * @author Ryan Heaton
 */
public abstract class GedcomxApplicationState {

  protected static final EmbeddedLinkLoader DEFAULT_EMBEDDED_LINK_LOADER = new EmbeddedLinkLoader();

  private static final String SDK_VERSION;
  static {
    String localSdkVersion = null;
    final String propertiesFile = "/META-INF/maven/org.gedcomx/gedcomx-rs-client/pom.properties";
    try (final InputStream propertiesInputStream = ClassLoader.class.getResourceAsStream(propertiesFile)) {
      final Properties properties = new Properties();
      properties.load(propertiesInputStream);
      localSdkVersion = properties.getProperty("version");
    }
    catch (IOException ioe) {
      // Intentionally do nothing
    }
    SDK_VERSION = StringUtils.isBlank(localSdkVersion) ? "UNKNOWN" : localSdkVersion;
  }

  protected final StateFactory stateFactory;
  protected final Map links;
  protected final Client client;
  protected final ClientRequest request;
  protected final ClientResponse response;
  protected final E entity;
  protected String accessToken;
  private ClientRequest lastEmbeddedRequest;
  private ClientResponse lastEmbeddedResponse;
  private final Set embeddedLinksLoaded = new TreeSet();

  protected GedcomxApplicationState(ClientRequest request, ClientResponse response, String accessToken, StateFactory stateFactory) {
    this.request = request;
    this.response = response;
    this.client = response.getClient();
    this.accessToken = accessToken;
    this.stateFactory = stateFactory;
    this.entity = loadEntityConditionally(this.response);
    List links = loadLinks(this.response, this.entity);
    this.links = new TreeMap();
    for (Link link : links) {
      this.links.put(link.getRel(), link);
    }
  }

  protected E loadEntityConditionally(ClientResponse response) {
    if (!HttpMethod.HEAD.equals(request.getMethod()) && !HttpMethod.OPTIONS.equals(request.getMethod()) && response.getClientResponseStatus() == ClientResponse.Status.OK) {
      return loadEntity(response);
    }
    else {
      return null;
    }
  }

  public GedcomxApplicationState inject(ClientRequest request) {
    return clone(request, invoke(request));
  }

  protected abstract GedcomxApplicationState clone(ClientRequest request, ClientResponse response);

  protected abstract E loadEntity(ClientResponse response);

  protected abstract SupportsLinks getMainDataElement();

  protected List loadLinks(ClientResponse response, E entity) {
    ArrayList links = new ArrayList();

    //if there's a location, we'll consider it a "self" link.
    if (response.getLocation() != null) {
      links.add(new Link(Rel.SELF, new org.gedcomx.common.URI(response.getLocation().toString())));
    }

    //initialize links with link headers
    List linkHeaders = response.getHeaders().get("Link");
    if (linkHeaders != null) {
      for (String header : linkHeaders) {
        LinkHeader lh = LinkHeader.valueOf(header);
        for (String rel : lh.getRel()) {
          Link link = new Link(rel, lh.getUri() == null ? null : org.gedcomx.common.URI.create(lh.getUri().toString()));
          link.setTemplate(lh.getParams().getFirst("template"));
          link.setTitle(lh.getParams().getFirst("title"));
          link.setAccept(lh.getParams().getFirst("accept"));
          link.setAllow(lh.getParams().getFirst("allow"));
          link.setHreflang(lh.getParams().getFirst("hreflang"));
          link.setType(lh.getParams().getFirst("type"));
          links.add(link);
        }
      }
    }

    //load the links from the main data element
    SupportsLinks mainElement = getMainDataElement();
    if (mainElement != null && mainElement.getLinks() != null) {
      links.addAll(mainElement.getLinks());
    }

    //load links at the document level
    if(entity != mainElement && entity instanceof SupportsLinks && ((SupportsLinks) entity).getLinks() != null) {
      links.addAll(((SupportsLinks)entity).getLinks());
    }


    return links;
  }

  public Client getClient() {
    return this.client; //we'll just use the descriptors client as ours.
  }

  public String getAccessToken() {
    return accessToken;
  }

  public boolean isAuthenticated() {
    return this.accessToken != null;
  }

  public ClientRequest getRequest() {
    return request.clone();
  }

  public ClientResponse getResponse() {
    return response;
  }

  public ClientRequest getLastEmbeddedRequest() {
    return lastEmbeddedRequest;
  }

  public ClientResponse getLastEmbeddedResponse() {
    return lastEmbeddedResponse;
  }

  public boolean isEmbeddedLinkLoaded(String rel) {
    return this.embeddedLinksLoaded.contains(rel);
  }

  public E getEntity() {
    return entity;
  }

  public SourceDescription getDescription() {
    if (this.entity instanceof Gedcomx) {
      Gedcomx gx = (Gedcomx) this.entity;
      org.gedcomx.common.URI descriptionRef = gx.getDescriptionRef();
      if (descriptionRef != null && descriptionRef.toString().startsWith("#") && gx.getSourceDescriptions() != null) {
        String descriptionId = descriptionRef.toString().substring(1);
        for (SourceDescription description : gx.getSourceDescriptions()) {
          if (descriptionId.equals(description.getId())) {
            return description;
          }
        }
      }
    }

    return null;
  }

  public URI getUri() {
    return this.request.getURI();
  }

  public boolean hasClientError() {
    return this.response.getStatus() >= 400 && this.response.getStatus() < 500;
  }

  public boolean hasServerError() {
    return this.response.getStatus() >= 500;
  }

  public boolean hasError() {
    return hasClientError() || hasServerError();
  }

  public boolean hasStatus(ClientResponse.Status status) {
    return status != null && status.equals(this.response.getClientResponseStatus());
  }

  public EntityTag getETag() {
    return this.response.getEntityTag();
  }

  public Date getLastModified() {
    return this.response.getLastModified();
  }

  public MultivaluedMap getHeaders() {
    return response.getHeaders();
  }

  public URI getSelfUri() {
    String selfRel = getSelfRel();
    Link link = null;
    if (selfRel != null) {
      link = getLink(selfRel);
    }
    link = link == null ? getLink(Rel.SELF) : link;
    URI self = link == null ? null : link.getHref() == null ? null : link.getHref().toURI();
    return self == null ? getUri() : self;
  }

  public String getSelfRel() {
    return null;
  }

  public GedcomxApplicationState head(StateTransitionOption... options) {
    ClientRequest.Builder builder = createAuthenticatedRequest();
    Object accept = this.request.getHeaders().getFirst("Accept");
    if (accept != null) {
      builder = builder.accept(String.valueOf(accept));
    }

    ClientRequest request = builder.build(getSelfUri(), HttpMethod.HEAD);
    return clone(request, invoke(request, options));
  }

  public GedcomxApplicationState get(StateTransitionOption... options) {
    ClientRequest.Builder builder = createAuthenticatedRequest();
    Object accept = this.request.getHeaders().getFirst("Accept");
    if (accept != null) {
      builder = builder.accept(String.valueOf(accept));
    }

    ClientRequest request = builder.build(getSelfUri(), HttpMethod.GET);
    ClientResponse response = invoke(request, options);
    return clone(request, response);
  }

  public GedcomxApplicationState delete(StateTransitionOption... options) {
    ClientRequest.Builder builder = createAuthenticatedRequest();
    Object accept = this.request.getHeaders().getFirst("Accept");
    if (accept != null) {
      builder = builder.accept(String.valueOf(accept));
    }
    ClientRequest request = builder.build(getSelfUri(), HttpMethod.DELETE);
    return clone(request, invoke(request, options));
  }

  public GedcomxApplicationState options(StateTransitionOption... options) {
    ClientRequest.Builder builder = createAuthenticatedRequest();
    Object accept = this.request.getHeaders().getFirst("Accept");
    if (accept != null) {
      builder = builder.accept(String.valueOf(accept));
    }
    ClientRequest request = builder.build(getSelfUri(), HttpMethod.OPTIONS);
    return clone(request, invoke(request, options));
  }

  public GedcomxApplicationState put(E entity, StateTransitionOption... options) {
    ClientRequest.Builder builder = createAuthenticatedRequest();
    Object accept = this.request.getHeaders().getFirst("Accept");
    Object contentType = this.request.getHeaders().getFirst("Content-Type");
    if (accept != null) {
      builder = builder.accept(String.valueOf(accept));
    }
    if (contentType != null) {
      builder = builder.type(String.valueOf(contentType));
    }
    ClientRequest request = builder.entity(entity).build(getSelfUri(), HttpMethod.PUT);
    return clone(request, invoke(request, options));
  }

  public GedcomxApplicationState post(E entity, StateTransitionOption... options) {
    ClientRequest.Builder builder = createAuthenticatedRequest();
    Object accept = this.request.getHeaders().getFirst("Accept");
    Object contentType = this.request.getHeaders().getFirst("Content-Type");
    if (accept != null) {
      builder = builder.accept(String.valueOf(accept));
    }
    if (contentType != null) {
      builder = builder.type(String.valueOf(contentType));
    }
    ClientRequest request = builder.entity(entity).build(getSelfUri(), HttpMethod.POST);
    return clone(request, invoke(request, options));
  }

  public List getWarnings() {
    ArrayList warnings = new ArrayList();
    List warningValues = this.response.getHeaders().get("Warning");
    if (warningValues != null) {
      for (String warningValue : warningValues) {
        warnings.add(HttpWarning.parse(warningValue));
      }
    }
    return warnings;
  }

  public Link getLink(String rel) {
    return this.links.get(rel);
  }

  public List getLinks() {
    return Arrays.asList(this.links.values().toArray(new Link[links.size()]));
  }

  public GedcomxApplicationState ifSuccessful() {
    if (hasError()) {
      throw new GedcomxApplicationException(buildFailureMessage(), this.response);
    }
    return this;
  }

  protected String buildFailureMessage() {
    Response.StatusType status = this.response.getStatusInfo();
    StringBuilder builder = new StringBuilder("Unsuccessful ").append(this.request.getMethod()).append(" to ").append(getUri()).append(" (").append(status.getStatusCode());
    if (status.getReasonPhrase() != null) {
      builder = builder.append(": ").append(status.getReasonPhrase());
    }
    builder = builder.append(")");

    List warnings = getWarnings();
    if (warnings != null && warnings.size() > 0) {
      for (HttpWarning warning : warnings) {
        builder.append("\nWarning: ").append(warning.getMessage());
      }
    }

    return builder.toString();
  }

  protected GedcomxApplicationState authenticateViaOAuth2Password(String username, String password, String clientId) {
    return authenticateViaOAuth2Password(username, password, clientId, null);
  }

  protected GedcomxApplicationState authenticateViaOAuth2Password(String username, String password, String clientId, String clientSecret) {
    MultivaluedMap formData = new MultivaluedMapImpl();
    formData.putSingle("grant_type", "password");
    formData.putSingle("username", username);
    formData.putSingle("password", password);
    formData.putSingle("client_id", clientId);
    if (clientSecret != null) {
      formData.putSingle("client_secret", clientSecret);
    }
    return authenticateViaOAuth2(formData);
  }

  protected GedcomxApplicationState authenticateViaOAuth2AuthCode(String authCode, String redirect, String clientId) {
    return authenticateViaOAuth2AuthCode(authCode, redirect, clientId, null);
  }

  protected GedcomxApplicationState authenticateViaOAuth2AuthCode(String authCode, String redirect, String clientId, String clientSecret) {
    MultivaluedMap formData = new MultivaluedMapImpl();
    formData.putSingle("grant_type", "authorization_code");
    formData.putSingle("code", authCode);
    formData.putSingle("redirect_uri", redirect);
    formData.putSingle("client_id", clientId);
    if (clientSecret != null) {
      formData.putSingle("client_secret", clientSecret);
    }
    return authenticateViaOAuth2(formData);
  }

  protected GedcomxApplicationState authenticateViaOAuth2ClientCredentials(String clientId, String clientSecret) {
    MultivaluedMap formData = new MultivaluedMapImpl();
    formData.putSingle("grant_type", "client_credentials");
    formData.putSingle("client_id", clientId);
    if (clientSecret != null) {
      formData.putSingle("client_secret", clientSecret);
    }
    return authenticateViaOAuth2(formData);
  }

  protected GedcomxApplicationState authenticateWithAccessToken(String accessToken) {
    this.accessToken = accessToken;
    return this;
  }

  protected GedcomxApplicationState authenticateViaOAuth2(MultivaluedMap formData, StateTransitionOption... options) {
    if (hasServerError()) {
      // first do a check to see if the server is in a bad state.
      throw new GedcomxApplicationException(buildFailureMessage(), this.response);
    }

    Link tokenLink = this.links.get(Rel.OAUTH2_TOKEN);
    if (tokenLink == null || tokenLink.getHref() == null) {
      throw new GedcomxApplicationException(String.format("No OAuth2 token URI supplied for resource at %s.", getUri()));
    }
    URI tokenUri = tokenLink.getHref().toURI();

    ClientRequest request = createRequest()
      .accept(MediaType.APPLICATION_JSON_TYPE)
      .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
      .entity(formData)
      .build(tokenUri, HttpMethod.POST);
    ClientResponse response = invoke(request, options);

    if (response.getStatus() >= 200 && response.getStatus() < 300) {
      ObjectNode accessToken = response.getEntity(ObjectNode.class);
      JsonNode access_token = accessToken.get("access_token");

      if (access_token == null) {
        //workaround to accommodate providers that were built on an older version of the oauth2 specification.
        access_token = accessToken.get("token");
      }

      if (access_token == null) {
        throw new GedcomxApplicationException("Illegal access token response: no access_token provided.", response);
      }

      return authenticateWithAccessToken(access_token.asText());
    }
    else {
      StringBuilder messageDetails = new StringBuilder();
      try {
        ObjectNode error = response.getEntity(ObjectNode.class);
        boolean hasErrorType = error.has("error");
        boolean hasErrorDescription = error.has("error_description");
        if (hasErrorType || hasErrorDescription) {
          messageDetails.append(" (");
          if (hasErrorType) {
            messageDetails.append(error.get("error"));
          }
          if (hasErrorType && hasErrorDescription) {
            messageDetails.append(": ");
          }
          if (hasErrorDescription) {
            messageDetails.append(error.get("error_description"));
          }
          messageDetails.append(')');
        }
      }
      catch (Exception e) {
        messageDetails.append(" (no details available)");
      }
      throw new GedcomxApplicationException(String.format("Unable to obtain an access token%s.", messageDetails), response);
    }
  }

  protected GedcomxApplicationState readPage(String rel, StateTransitionOption... options) {
    Link link = getLink(rel);
    if (link == null || link.getHref() == null) {
      return null;
    }

    ClientRequest.Builder builder = createAuthenticatedRequest();
    Object accept = this.request.getHeaders().getFirst("Accept");
    Object contentType = this.request.getHeaders().getFirst("Content-Type");
    if (accept != null) {
      builder = builder.accept(String.valueOf(accept));
    }
    if (contentType != null) {
      builder = builder.type(String.valueOf(contentType));
    }
    ClientRequest request = builder.build(link.getHref().toURI(), HttpMethod.GET);
    return clone(request, invoke(request, options));
  }

  protected GedcomxApplicationState readNextPage(StateTransitionOption... options) {
    return readPage(Rel.NEXT, options);
  }

  protected GedcomxApplicationState readPreviousPage(StateTransitionOption... options) {
    return readPage(Rel.PREVIOUS, options);
  }

  protected GedcomxApplicationState readFirstPage(StateTransitionOption... options) {
    return readPage(Rel.FIRST, options);
  }

  protected GedcomxApplicationState readLastPage(StateTransitionOption... options) {
    return readPage(Rel.LAST, options);
  }

  protected ClientRequest.Builder createAuthenticatedFeedRequest() {
    return createAuthenticatedRequest().accept(AtomModel.ATOM_GEDCOMX_JSON_MEDIA_TYPE).type(AtomModel.ATOM_GEDCOMX_JSON_MEDIA_TYPE);
  }

  protected ClientRequest.Builder createAuthenticatedGedcomxRequest() {
    return createAuthenticatedRequest().accept(GedcomxConstants.GEDCOMX_JSON_MEDIA_TYPE).type(GedcomxConstants.GEDCOMX_JSON_MEDIA_TYPE);
  }

  protected ClientResponse invoke(ClientRequest request, StateTransitionOption... options) {
    for (StateTransitionOption option : options) {
      option.apply(request);
    }
    return getClient().handle(request);
  }

  protected ClientRequest.Builder createRequest() {
    return ClientRequest.create()
        .header(HttpHeaders.USER_AGENT, "gedcomx-java-sdk/" + SDK_VERSION);
  }

  protected ClientRequest.Builder createAuthenticatedRequest() {
    ClientRequest.Builder request = createRequest();
    if (this.accessToken != null) {
      request = request.header("Authorization", "Bearer " + this.accessToken);
    }
    return request;
  }

  protected void includeEmbeddedResources(Gedcomx entity, StateTransitionOption... options) {
    embed(getEmbeddedLinkLoader().loadEmbeddedLinks(entity), entity, options);
  }

  protected void embed(List links, Gedcomx entity, StateTransitionOption... options) {
    for (Link link : links) {
      embed(link, entity, options);
    }
  }

  protected EmbeddedLinkLoader getEmbeddedLinkLoader() {
    return DEFAULT_EMBEDDED_LINK_LOADER;
  }

  protected void embed(Link link, Gedcomx entity, StateTransitionOption... options) {
    String rel = link.getRel();
    if (rel != null && isEmbeddedLinkLoaded(rel)) {
      return;
    }

    if (link.getHref() != null) {
      lastEmbeddedRequest = createRequestForEmbeddedResource(rel).build(link.getHref().toURI(), HttpMethod.GET);
      lastEmbeddedResponse = invoke(lastEmbeddedRequest, options);
      if (lastEmbeddedResponse.getClientResponseStatus() == ClientResponse.Status.OK) {
        entity.embed(lastEmbeddedResponse.getEntity(Gedcomx.class));
        if (rel != null) {
          this.embeddedLinksLoaded.add(rel);
        }
      }
      else if (lastEmbeddedResponse.getStatus() >= 500) {
        throw new GedcomxApplicationException(String.format("Unable to load embedded resources: server says \"%s\" at %s.", lastEmbeddedResponse.getStatus(), lastEmbeddedRequest.getURI()), lastEmbeddedResponse);
      }
      else {
        //todo: log a warning? throw an error?
      }
    }
  }

  protected ClientRequest.Builder createRequestForEmbeddedResource(String rel) {
    return createAuthenticatedGedcomxRequest();
  }

  public AgentState readContributor(StateTransitionOption... options) {
    SupportsLinks scope = getMainDataElement();
    if (scope instanceof Attributable) {
      return readContributor((Attributable) scope, options);
    }
    else {
      return null;
    }
  }

  public AgentState readContributor(Attributable attributable, StateTransitionOption... options) {
    Attribution attribution = attributable.getAttribution();
    if (attribution == null) {
      return null;
    }

    return readContributor(attribution.getContributor(), options);
  }

  public AgentState readContributor(ResourceReference contributor, StateTransitionOption... options) {
    if (contributor == null || contributor.getResource() == null) {
      return null;
    }

    ClientRequest request = createAuthenticatedGedcomxRequest().build(contributor.getResource().toURI(), HttpMethod.GET);
    return this.stateFactory.newAgentState(request, invoke(request, options), this.accessToken);
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy