Package org.openmhealth.reference.servlet

Source Code of org.openmhealth.reference.servlet.Version1

/*******************************************************************************
* Copyright 2013 Open mHealth
*
* 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.openmhealth.reference.servlet;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
import org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.common.error.OAuthError.CodeResponse;
import org.apache.oltu.oauth2.common.error.OAuthError.TokenResponse;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.apache.oltu.oauth2.common.message.types.ResponseType;
import org.apache.oltu.oauth2.common.message.types.TokenType;
import org.openmhealth.reference.data.AuthorizationCodeBin;
import org.openmhealth.reference.data.AuthorizationCodeResponseBin;
import org.openmhealth.reference.data.AuthorizationTokenBin;
import org.openmhealth.reference.data.Registry;
import org.openmhealth.reference.data.ThirdPartyBin;
import org.openmhealth.reference.data.UserBin;
import org.openmhealth.reference.domain.AuthenticationToken;
import org.openmhealth.reference.domain.AuthorizationCode;
import org.openmhealth.reference.domain.AuthorizationCodeResponse;
import org.openmhealth.reference.domain.AuthorizationToken;
import org.openmhealth.reference.domain.Data;
import org.openmhealth.reference.domain.MultiValueResult;
import org.openmhealth.reference.domain.ThirdParty;
import org.openmhealth.reference.domain.User;
import org.openmhealth.reference.exception.OmhException;
import org.openmhealth.reference.filter.AuthFilter;
import org.openmhealth.reference.request.AuthenticationRequest;
import org.openmhealth.reference.request.DataReadRequest;
import org.openmhealth.reference.request.DataWriteRequest;
import org.openmhealth.reference.request.ListRequest;
import org.openmhealth.reference.request.OauthRegistrationRequest;
import org.openmhealth.reference.request.Request;
import org.openmhealth.reference.request.SchemaIdsRequest;
import org.openmhealth.reference.request.SchemaRequest;
import org.openmhealth.reference.request.SchemaVersionsRequest;
import org.openmhealth.reference.request.UserActivationRequest;
import org.openmhealth.reference.request.UserRegistrationRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

/**
* <p>
* The controller for the version 1 of the Open mHealth API.
* </p>
*
* <p>
* This class has no state and, therefore, is immutable.
* </p>
*
* @author John Jenkins
*/
@Controller
@RequestMapping(Version1.PATH)
public class Version1 {
  /**
   * The root path for queries to this version of the API.
   */
  public static final String PATH = "/v1";

  /**
   * The path to the user registration end-point.
   */
  public static final String PATH_REGISTRATION = "/users/registration";
  /**
   * The path to the user activation end-point.
   */
  public static final String PATH_ACTIVATION = "/users/activation";
 
  /**
   * The username parameter for the authenticate requests.
   */
  public static final String PARAM_AUTHENTICATION_USERNAME = "username";
  /**
   * The password parameter for the authenticate requests.
   */
  public static final String PARAM_AUTHENTICATION_PASSWORD = "password";
  /**
   * The authentication token parameter for requests that require
   * authentication.
   */
  public static final String PARAM_AUTHENTICATION_AUTH_TOKEN =
    "omh_auth_token";
  /**
   * The authorization flag that indicates if the user granted the
   * third-party access.
   */
  public static final String PARAM_AUTHORIZATION_GRANTED = "granted";
  /**
   * The key for the code parameter when the user is responding to a request
   * to grant access to a third-party.
   */
  public static final String PARAM_AUTHORIZATION_CODE = "code";

  /**
   * The parameter for the number of records to skip in requests that use
   * paging.
   */
  public static final String PARAM_PAGING_NUM_TO_SKIP = "num_to_skip";
  /**
   * The parameter for the number of records to return in requests that use
   * paging.
   */
  public static final String PARAM_PAGING_NUM_TO_RETURN = "num_to_return";
 
  /**
   * The parameter for the unique identifier for a schema. This is sometimes
   * used as part of the URI for the RESTful implementation.
   */
  public static final String PARAM_SCHEMA_ID = "schema_id";
  /**
   * The parameter for the version of a schema. This is sometimes used as
   * part of the URI for the RESTful implementation.
   */
  public static final String PARAM_SCHEMA_VERSION = "schema_version";
 
  /**
   * A parameter that limits the results to only those that were created on
   * or after the given date.
   */
  public static final String PARAM_DATE_START = "t_start";
  /**
   * A parameter that limits the results to only those that were created on
   * or before the given date.
   */
  public static final String PARAM_DATE_END = "t_end";
 
  /**
   * The parameter that indicates to which user the data should pertain.
   */
  public static final String PARAM_OWNER = "owner";
  /**
   * The parameter that indicates that the data should be summarized, if
   * possible.
   */
  public static final String PARAM_SUMMARIZE = "summarize";
  /**
   * The parameter that indicates which columns of the data should be
   * returned.
   */
  public static final String PARAM_COLUMN_LIST = "column_list";
 
  /**
   * The parameter for the data when it is being uploaded.
   */
  public static final String PARAM_DATA = "data";

  /**
   * The header for the URL to the previous set of data for list requests.
   */
  public static final String HEADER_PREVIOUS = "Previous";
  /**
   * The header for the URL to the next set of data for list requests.
   */
  public static final String HEADER_NEXT = "Next";
 
  /**
   * The encoding for the previous and next URLs.
   */
  private static final String URL_ENCODING_UTF_8 = "UTF-8";
 
  /**
   * The logger for this class.
   */
  private static final Logger LOGGER =
    Logger.getLogger(Version1.class.getCanonicalName());
 
  /**
   * Creates an authentication request, authenticates the user and, if
   * successful, returns the user's credentials.
   *
   * @param username
   *        The username of the user attempting to authenticate.
   *
   * @param password
   *        The password of the user attempting to authenticate.
   *
   * @param request
   *        The HTTP request object.
   *
   * @param response
   *        The HTTP response object.
   *
   * @return The authorization token.
   *
   * @throws OmhException
   *         There was a problem with the request. This could be any of the
   *         sub-classes of {@link OmhException}.
   */
  @RequestMapping(value = "auth", method = RequestMethod.POST)
  public @ResponseBody String getAuthentication(
    @RequestParam(
      value = PARAM_AUTHENTICATION_USERNAME,
      required = true)
      final String username,
    @RequestParam(
      value = PARAM_AUTHENTICATION_PASSWORD,
      required = true)
      final String password,
    final HttpServletRequest request,
    final HttpServletResponse response)
    throws OmhException {
   
    // Create the authentication request from parameters.
    AuthenticationToken token =
      handleRequest(
        request,
        response,
        new AuthenticationRequest(username, password));
   
    // Add a cookie for the authentication token.
    Cookie cookie =
      new Cookie(PARAM_AUTHENTICATION_AUTH_TOKEN, token.getToken());
    // Set the expiration on the cookie.
    cookie
      .setMaxAge(
        new Long(
          (token.getExpires() - System.currentTimeMillis()) / 1000)
        .intValue());
    // Build the path without the "auth" part.
    String requestUri = request.getRequestURI();
    cookie.setPath(requestUri.substring(0, requestUri.length() - 5));
    // Make sure the cookie is only used with HTTPS.
    cookie.setSecure(true);
    // Add the cookie to the response.
    response.addCookie(cookie);
   
    // Return the token.
    return token.getToken();
  }

  /**
   * Creates a registration request for a third-party ("client" in OAuth
   * parlance).
   *
   * @param name
   *        The third-party's name.
   *
   * @param description
   *        The third-party's description.
   *
   * @param redirectUri
   *        The location to redirect the user to after they have responded to
   *        an authorization request from this third-party.
   *       
   * @param request
   *        The HTTP request.
   *
   * @param response
   *        The HTTP response.
   */
  @RequestMapping(
    value = "auth/oauth/registration",
    method = RequestMethod.POST)
  public @ResponseBody ThirdParty thirdPartyRegistration(
    @RequestParam(
      value = ThirdParty.JSON_KEY_NAME,
      required = true)
      final String name,
    @RequestParam(
      value = ThirdParty.JSON_KEY_DESCRIPTION,
      required = true)
      final String description,
    @RequestParam(
      value = ThirdParty.JSON_KEY_REDIRECT_URI,
      required = true)
      final String redirectUri,
    final HttpServletRequest request,
    final HttpServletResponse response) {
   
    // Make sure the authentication token was a parameter. This prevents
    // malicious code from "hijacking" the token by performing a POST and
    // having the browser inject it as only a cookie.
    if(!
      (Boolean)
        request
          .getAttribute(
            AuthFilter
              .ATTRIBUTE_AUTHENTICATION_TOKEN_IS_PARAM)) {
     
      throw
        new OmhException(
          "To register a third-party, the authentication token is " +
            "required as a parameter.");
    }
   
    return
      handleRequest(
        request,
        response,
        new OauthRegistrationRequest(
          (AuthenticationToken)
            request
              .getAttribute(
                AuthFilter
                  .ATTRIBUTE_AUTHENTICATION_TOKEN),
          name,
          description,
          redirectUri));
  }
 
  /**
   * <p>
   * The OAuth call where a user has been redirected to us by some
   * third-party in order for us to present them with an authorization
   * request, verify that the user is who they say they are, and grant or
   * deny the request.
   * </p>
   *
   * <p>
   * This call will either redirect the user to the authorization HTML page
   * with the parameters embedded or it will return a non-2xx response with a
   * message indicating what was wrong with the request. Unfortunately,
   * because the problem with the request may be that the given client ID is
   * unknown, we have no way to direct the user back. If we simply force the
   * browser to "go back", it may result in an infinite loop where the
   * third-party continuously redirects them back to us and visa-versa. To
   * avoid this, we should simply return an error string and let the user
   * decide.
   * </p>
   *
   * @param request
   *        The HTTP request.
   *
   * @param response
   *        The HTTP response.
   *
   * @return A OAuth-specified JSON response that indicates what was wrong
   *         with the request. If nothing was wrong with the request, a
   *         redirect would have been returned.
   *
   * @throws IOException
   *         There was a problem responding to the client.
   *
   * @throws OAuthSystemException
   *         The OAuth library encountered an error.
   */
  @RequestMapping(
    value = "auth/oauth/authorize",
    method = { RequestMethod.GET, RequestMethod.POST })
  public @ResponseBody String receiveAuthorizationCodeRequest(
    final HttpServletRequest request,
    final HttpServletResponse response)
    throws IOException, OAuthSystemException {
   
    // Create the OAuth request from the HTTP request.
    OAuthAuthzRequest oauthRequest;
    try {
      oauthRequest = new OAuthAuthzRequest(request);
    }
    // The request does not conform to the RFC, so we return a HTTP 400
    // with a reason.
    catch(OAuthProblemException e) {
      // Create the OAuth response.
      OAuthResponse oauthResponse =
        OAuthASResponse
          .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
          .error(e)
          .buildJSONMessage();
     
      // Set the status and return the error message.
      response.setStatus(oauthResponse.getResponseStatus());
      return oauthResponse.getBody();
    }
   
    // Validate that the user is requesting a "code" response type, which
    // is the only response type we accept.
    try {
      if(!
        ResponseType
          .CODE.toString().equals(oauthRequest.getResponseType())) {
       
        // Create the OAuth response.
        OAuthResponse oauthResponse =
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
            .setError(CodeResponse.UNSUPPORTED_RESPONSE_TYPE)
            .setErrorDescription(
              "The response type must be '" +
                ResponseType.CODE.toString() +
                "' but was instead: " +
                oauthRequest.getResponseType())
            .setState(oauthRequest.getState())
            .buildJSONMessage();
       
        // Set the status and return the error message.
        response.setStatus(oauthResponse.getResponseStatus());
        return oauthResponse.getBody();
      }
    }
    catch(IllegalArgumentException e) {
      // Create the OAuth response.
      OAuthResponse oauthResponse =
        OAuthASResponse
          .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
          .setError(CodeResponse.UNSUPPORTED_RESPONSE_TYPE)
          .setErrorDescription(
            "The response type is unknown: " +
              oauthRequest.getResponseType())
          .setState(oauthRequest.getState())
          .buildJSONMessage();
     
      // Set the status and return the error message.
      response.setStatus(oauthResponse.getResponseStatus());
      return oauthResponse.getBody();
    }
   
    // Make sure no redirect URI was given.
    if(oauthRequest.getRedirectURI() != null) {
      // Create the OAuth response.
      OAuthResponse oauthResponse =
        OAuthASResponse
          .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
          .setError(CodeResponse.INVALID_REQUEST)
          .setErrorDescription(
            "A URI must not be given. Instead, the one given " +
              "when the account was created will be used.")
          .setState(oauthRequest.getState())
          .buildJSONMessage();
     
      // Set the status and return the error message.
      response.setStatus(oauthResponse.getResponseStatus());
      return oauthResponse.getBody();
    }
   
    // Attempt to get the third-party.
    ThirdParty thirdParty =
      ThirdPartyBin
        .getInstance().getThirdParty(oauthRequest.getClientId());
    // If the third-party is unknown, reject the request.
    if(thirdParty == null) {
      // Create the OAuth response.
      OAuthResponse oauthResponse =
        OAuthASResponse
          .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
          .setError(CodeResponse.INVALID_REQUEST)
          .setErrorDescription(
            "The client ID is unknown: " +
              oauthRequest.getClientId())
          .setState(oauthRequest.getState())
          .buildJSONMessage();
     
      // Set the status and return the error message.
      response.setStatus(oauthResponse.getResponseStatus());
      return oauthResponse.getBody();
    }
   
    // Attempt to get the scopes.
    Set<String> scopes = oauthRequest.getScopes();
    if((scopes == null) || (scopes.size() == 0)) {
      // Create the OAuth response.
      OAuthResponse oauthResponse =
        OAuthASResponse
          .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
          .setError(CodeResponse.INVALID_SCOPE)
          .setErrorDescription("A scope is required.")
          .setState(oauthRequest.getState())
          .buildJSONMessage();
     
      // Set the status and return the error message.
      response.setStatus(oauthResponse.getResponseStatus());
      return oauthResponse.getBody();
    }
    // Validate the scopes.
    Registry registry = Registry.getInstance();
    for(String scope : scopes) {
      if(registry.getSchemas(scope, null, 0, 1).size() != 1) {
        // Create the OAuth response.
        OAuthResponse oauthResponse =
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
            .setError(CodeResponse.INVALID_SCOPE)
            .setErrorDescription(
              "Each scope must be a known schema ID: " + scope)
            .setState(oauthRequest.getState())
            .buildJSONMessage();
       
        // Set the status and return the error message.
        response.setStatus(oauthResponse.getResponseStatus());
        return oauthResponse.getBody();
      }
    }
   
    // Create the temporary code to be granted or rejected by the user.
    AuthorizationCode code =
      new AuthorizationCode(
        thirdParty,
        oauthRequest.getScopes(),
        oauthRequest.getState());
   
    // Store the authorization code.
    AuthorizationCodeBin.getInstance().storeCode(code);
   
    // Build the scope as specified by the OAuth specification.
    StringBuilder scopeBuilder = new StringBuilder();
    for(String scope : code.getScopes()) {
      // Add a space unless it's the first entity.
      if(scopeBuilder.length() != 0) {
        scopeBuilder.append(' ');
      }
      // Add the scope.
      scopeBuilder.append(scope);
    }
   
    // Set the redirect.
    response
      .sendRedirect(
        OAuthASResponse
          .authorizationResponse(
            request,
            HttpServletResponse.SC_FOUND)
          .setCode(code.getCode())
          .location("Authorize.html")
          .setScope(scopeBuilder.toString())
          .setParam(ThirdParty.JSON_KEY_NAME, thirdParty.getName())
          .setParam(
            ThirdParty.JSON_KEY_DESCRIPTION,
            thirdParty.getDescription())
          .buildQueryMessage()
          .getLocationUri());
    // Since we are redirecting the user, we don't need to return anything.
    return null;
  }
 
  /**
   * <p>
   * Handles the response from the user regarding whether or not the user
   * granted permission to a third-party via OAuth. If the user's credentials
   * are invalid or there was a general error reading the request, an error
   * message will be returned and displayed to the user. Once we have the
   * third-party's information, we will do a best-effort to redirect the user
   * back to the third-party with a code, which the third-party can then use
   * to call us later to determine the actual failure.
   * </p>
   *
   * @param username
   *        The user's username.
   *
   * @param password
   *        The user's password.
   *
   * @param granted
   *        Whether or not the permission was granted.
   *
   * @param code
   *        The code that was created, but not yet validated by the user.
   *
   * @param request
   *        The HTTP request object.
   *
   * @param response
   *        The HTTP response object.
   */
  @RequestMapping(
    value = "auth/oauth/authorization",
    method = RequestMethod.POST)
  public void authenticateAuthorizationCodeRequest(
    @RequestParam(
      value = PARAM_AUTHENTICATION_USERNAME,
      required = true)
      final String username,
    @RequestParam(
      value = PARAM_AUTHENTICATION_PASSWORD,
      required = true)
      final String password,
    @RequestParam(
      value = PARAM_AUTHORIZATION_GRANTED,
      required = true)
      final boolean granted,
    @RequestParam(
      value = PARAM_AUTHORIZATION_CODE,
      required = false)
      final String code,
    final HttpServletRequest request,
    final HttpServletResponse response)
    throws IOException, OAuthSystemException {
   
    // Get the user. If the user's credentials are invalid for whatever
    // reason, an exception will be thrown and the page will echo back the
    // reason.
    User user = AuthenticationRequest.getUser(username, password);
   
    // Get the authorization code.
    AuthorizationCode authCode =
      AuthorizationCodeBin.getInstance().getCode(code);
    // If the code is unknown, we cannot redirect back to the third-party
    // because we don't know who they are.
    if(authCode == null) {
      throw new OmhException("The authorization code is unknown.");
    }
   
    // Verify that the code has not yet expired.
    if(System.currentTimeMillis() > authCode.getExpirationTime()) {
      response
        .sendRedirect(
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
            .setError(CodeResponse.ACCESS_DENIED)
            .setErrorDescription("The code has expired.")
            .location(
              authCode
                .getThirdParty().getRedirectUri().toString())
            .setState(authCode.getState())
            .buildQueryMessage()
            .getLocationUri());
      return;
    }
   
    // Get the response if it already exists.
    AuthorizationCodeResponse codeResponse =
      AuthorizationCodeResponseBin.getInstance().getResponse(code);
   
    // If the response does not exist, attempt to create a new one and
    // save it.
    if(codeResponse == null) {
      // Create the new code.
      codeResponse =
        new AuthorizationCodeResponse(authCode, user, granted);
     
      // Store it.
      AuthorizationCodeResponseBin
        .getInstance().storeVerification(codeResponse);
    }
    // Make sure it is being verified by the same user.
    else if(
      ! user
        .getUsername().equals(codeResponse.getOwner().getUsername())) {
     
      response
        .sendRedirect(
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
            .setError(CodeResponse.ACCESS_DENIED)
            .setErrorDescription(
              "The code has already been verified by another " +
                "user.")
            .location(
              authCode
                .getThirdParty().getRedirectUri().toString())
            .setState(authCode.getState())
            .buildQueryMessage()
            .getLocationUri());
    }
    // Make sure the same grant response is being made.
    else if(granted == codeResponse.getGranted()) {
      response
        .sendRedirect(
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
            .setError(CodeResponse.ACCESS_DENIED)
            .setErrorDescription(
              "The user has re-submitted the same " +
                "authorization code twice with competing " +
                "grant values.")
            .location(
              authCode
                .getThirdParty().getRedirectUri().toString())
            .setState(authCode.getState())
            .buildQueryMessage()
            .getLocationUri());
    }
    // Otherwise, this is simply a repeat of the same request as before,
    // and we can simply ignore it.
   
    // Redirect the user back to the third-party with the authorization
    // code and state.
    response
      .sendRedirect(
        OAuthASResponse
          .authorizationResponse(
            request,
            HttpServletResponse.SC_OK)
          .location(
            authCode.getThirdParty().getRedirectUri().toString())
          .setCode(authCode.getCode())
          .setParam("state", authCode.getState())
          .buildQueryMessage()
          .getLocationUri());
  }
 
  /**
   * <p>
   * The OAuth call when a third-party is attempting to exchange their
   * authorization request token for a valid authorization token. Because
   * this is a back-channel communication from the third-party, their ID and
   * secret must be given to authenticate them. They will then be returned
   * either an authorization token or an error message indicating what was
   * wrong with the request.
   * </p>
   *
   * @param request
   *        The HTTP request object.
   *
   * @param response
   *        The HTTP response object.
   *
   * @return An OAuth-specified JSON error message or an OAuth-specified JSON
   *         response that includes the access and refresh tokens as well as
   *         the expiration and other information.
   *
   * @throws OAuthSystemException
   *         The OAuth library encountered an error.
   */
  @RequestMapping(
    value = "auth/oauth/token",
    method = RequestMethod.POST)
  public @ResponseBody String createAuthorizationToken(
    final HttpServletRequest request,
    final HttpServletResponse response)
    throws OAuthSystemException, IOException {
   
    // Attempt to build an OAuth request from the HTTP request.
    OAuthTokenRequest oauthRequest;
    try {
      oauthRequest = new OAuthTokenRequest(request);
      }
    // If the HTTP request was not a valid OAuth token request, then we
    // have no other choice but to reject it as a bad request.
    catch(OAuthProblemException e) {
      // Build the OAuth response.
          OAuthResponse oauthResponse =
            OAuthResponse
                .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                .error(e)
                .buildJSONMessage();

          // Set the HTTP response status code from the OAuth response.
          response.setStatus(oauthResponse.getResponseStatus());
         
          // Return the error message.
          return oauthResponse.getBody();
      }
   
    // Attempt to get the client.
    ThirdParty thirdParty =
      ThirdPartyBin
        .getInstance().getThirdParty(oauthRequest.getClientId());
    // If the client is unknown, respond as such.
    if(thirdParty == null) {
      // Create the OAuth response.
      OAuthResponse oauthResponse =
        OAuthASResponse
          .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
          .setError(TokenResponse.INVALID_CLIENT)
          .setErrorDescription(
            "The client is unknown: " + oauthRequest.getClientId())
          .buildJSONMessage();
     
      // Set the status and return the error message.
      response.setStatus(oauthResponse.getResponseStatus());
      return oauthResponse.getBody();
    }
   
    // Get the given client secret.
    String thirdPartySecret = oauthRequest.getClientSecret();
    if(thirdPartySecret == null) {
      // Create the OAuth response.
      OAuthResponse oauthResponse =
        OAuthASResponse
          .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
          .setError(TokenResponse.INVALID_CLIENT)
          .setErrorDescription("The client secret is required.")
          .buildJSONMessage();
     
      // Set the status and return the error message.
      response.setStatus(oauthResponse.getResponseStatus());
      return oauthResponse.getBody();
    }
    // Make sure the client gave the right secret.
    else if(! thirdPartySecret.equals(thirdParty.getSecret())) {
      // Create the OAuth response.
      OAuthResponse oauthResponse =
        OAuthASResponse
          .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
          .setError(TokenResponse.INVALID_CLIENT)
          .setErrorDescription("The client secret is incorrect.")
          .buildJSONMessage();
     
      // Set the status and return the error message.
      response.setStatus(oauthResponse.getResponseStatus());
      return oauthResponse.getBody();
    }
   
    // Get the grant-type.
    GrantType grantType;
    String grantTypeString = oauthRequest.getGrantType();
    if(GrantType.AUTHORIZATION_CODE.toString().equals(grantTypeString)) {
      grantType = GrantType.AUTHORIZATION_CODE;
    }
    else if(GrantType.CLIENT_CREDENTIALS.toString().equals(grantTypeString)) {
      grantType = GrantType.CLIENT_CREDENTIALS;
    }
    else if(GrantType.PASSWORD.toString().equals(grantTypeString)) {
      grantType = GrantType.PASSWORD;
    }
    else if(GrantType.REFRESH_TOKEN.toString().equals(grantTypeString)) {
      grantType = GrantType.REFRESH_TOKEN;
    }
    else {
      // Create the OAuth response.
      OAuthResponse oauthResponse =
        OAuthASResponse
          .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
          .setError(TokenResponse.INVALID_GRANT)
          .setErrorDescription(
            "The grant type is unknown: " + grantTypeString)
          .buildJSONMessage();
     
      // Set the status and return the error message.
      response.setStatus(oauthResponse.getResponseStatus());
      return oauthResponse.getBody();
    }
   
    // Handle the different types of token requests.
    AuthorizationToken token;
    if(GrantType.AUTHORIZATION_CODE.equals(grantType)) {
      // Attempt to get the code.
      String codeString = oauthRequest.getCode();
      if(codeString == null) {
        // Create the OAuth response.
        OAuthResponse oauthResponse =
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
            .setError(TokenResponse.INVALID_REQUEST)
            .setErrorDescription(
              "An authorization code must be given to be " +
                "exchanged for an authorization token.")
            .buildJSONMessage();
       
        // Set the status and return the error message.
        response.setStatus(oauthResponse.getResponseStatus());
        return oauthResponse.getBody();
      }
     
      // Attempt to lookup the actual AuthorizationCode object.
      AuthorizationCode code =
        AuthorizationCodeBin.getInstance().getCode(codeString);
      // If the code doesn't exist, reject the request.
      if(code == null) {
        // Create the OAuth response.
        OAuthResponse oauthResponse =
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
            .setError(TokenResponse.INVALID_REQUEST)
            .setErrorDescription(
              "The given authorization code is unknown: " +
                codeString)
            .buildJSONMessage();
       
        // Set the status and return the error message.
        response.setStatus(oauthResponse.getResponseStatus());
        return oauthResponse.getBody();
      }
     
      // Verify that the client asking for a token is the same as the one
      // that requested the code.
      if(! code.getThirdParty().getId().equals(thirdParty.getId())) {
        // Create the OAuth response.
        OAuthResponse oauthResponse =
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
            .setError(TokenResponse.INVALID_REQUEST)
            .setErrorDescription(
              "This client is not allowed to reference this " +
                "code: " +
                codeString)
            .buildJSONMessage();
       
        // Set the status and return the error message.
        response.setStatus(oauthResponse.getResponseStatus());
        return oauthResponse.getBody();
      }

      // If the code has expired, reject the request.
      if(System.currentTimeMillis() > code.getExpirationTime()) {
        // Create the OAuth response.
        OAuthResponse oauthResponse =
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
            .setError(TokenResponse.INVALID_REQUEST)
            .setErrorDescription(
              "The given authorization code has expired: " +
                codeString)
            .buildJSONMessage();
       
        // Set the status and return the error message.
        response.setStatus(oauthResponse.getResponseStatus());
        return oauthResponse.getBody();
      }
     
      // Use the code to lookup the response information and error out if
      // a user has not yet verified it.
      AuthorizationCodeResponse codeResponse =
        AuthorizationCodeResponseBin
          .getInstance().getResponse(code.getCode());
      if(codeResponse == null) {
        // Create the OAuth response.
        OAuthResponse oauthResponse =
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
            .setError(TokenResponse.INVALID_REQUEST)
            .setErrorDescription(
              "A user has not yet verified the code: " +
                codeString)
            .buildJSONMessage();
       
        // Set the status and return the error message.
        response.setStatus(oauthResponse.getResponseStatus());
        return oauthResponse.getBody();
      }
     
      // Determine if the user granted access and, if not, error out.
      if(! codeResponse.getGranted()) {
        // Create the OAuth response.
        OAuthResponse oauthResponse =
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
            .setError(TokenResponse.INVALID_REQUEST)
            .setErrorDescription(
              "The user denied the authorization: " + codeString)
            .buildJSONMessage();
       
        // Set the status and return the error message.
        response.setStatus(oauthResponse.getResponseStatus());
        return oauthResponse.getBody();
      }
     
      // Create a new token.
      token = new AuthorizationToken(codeResponse);
    }
    // Handle a third-party refreshing an existing token.
    else if(GrantType.REFRESH_TOKEN.equals(grantType)) {
      // Get the refresh token from the request.
      String refreshToken = oauthRequest.getRefreshToken();
      if(refreshToken == null) {
        // Create the OAuth response.
        OAuthResponse oauthResponse =
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
            .setError(TokenResponse.INVALID_REQUEST)
            .setErrorDescription(
              "An refresh token must be given to be exchanged " +
                "for a new authorization token.")
            .buildJSONMessage();
       
        // Set the status and return the error message.
        response.setStatus(oauthResponse.getResponseStatus());
        return oauthResponse.getBody();
      }
      // Use the refresh token to lookup the actual refresh token.
      AuthorizationToken currentToken =
        AuthorizationTokenBin
          .getInstance().getTokenFromRefreshToken(refreshToken);
      if(currentToken == null) {
        // Create the OAuth response.
        OAuthResponse oauthResponse =
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
            .setError(TokenResponse.INVALID_REQUEST)
            .setErrorDescription("The refresh token is unknown.")
            .buildJSONMessage();
       
        // Set the status and return the error message.
        response.setStatus(oauthResponse.getResponseStatus());
        return oauthResponse.getBody();
      }
     
      // Verify that the client asking for a token is the same as the one
      // that was issued the refresh token.
      // This is probably a very serious offense and should probably
      // raise some serious red flags!
      if(!
        currentToken
          .getThirdParty().getId().equals(thirdParty.getId())) {
       
        // Create the OAuth response.
        OAuthResponse oauthResponse =
          OAuthASResponse
            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
            .setError(TokenResponse.INVALID_REQUEST)
            .setErrorDescription(
              "This token does not belong to this client.")
            .buildJSONMessage();
       
        // Set the status and return the error message.
        response.setStatus(oauthResponse.getResponseStatus());
        return oauthResponse.getBody();
      }
     
      // Create a new authorization token from the current one.
      token = new AuthorizationToken(currentToken);
    }
    // If the grant-type is unknown, then we do not yet understand how
    // the request is built and, therefore, can do nothing more than
    // reject it via an OmhException.
    else {
      // Create the OAuth response.
      OAuthResponse oauthResponse =
        OAuthASResponse
          .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
          .setError(TokenResponse.UNSUPPORTED_GRANT_TYPE)
          .setErrorDescription(
            "The grant type must be one of '" +
              GrantType.AUTHORIZATION_CODE.toString() +
              "' or '" +
              GrantType.REFRESH_TOKEN.toString() +
              "': " +
              grantType.toString())
          .buildJSONMessage();
     
      // Set the status and return the error message.
      response.setStatus(oauthResponse.getResponseStatus());
      return oauthResponse.getBody();
    }
   
    // Store the new token.
    AuthorizationTokenBin.getInstance().storeToken(token);
   
    // Build the response.
    OAuthResponse oauthResponse =
      OAuthASResponse
        .tokenResponse(HttpServletResponse.SC_OK)
        .setAccessToken(token.getAccessToken())
        .setExpiresIn(Long.valueOf(token.getExpirationIn() / 1000).toString())
        .setRefreshToken(token.getRefreshToken())
        .setTokenType(TokenType.BEARER.toString())
        .buildJSONMessage();
   
    // Set the status.
    response.setStatus(oauthResponse.getResponseStatus());
   
    // Set the content-type.
    response.setContentType("application/json");

    // Add the headers.
    Map<String, String> headers = oauthResponse.getHeaders();
    for(String headerKey : headers.keySet()) {
      response.addHeader(headerKey, headers.get(headerKey));
    }
   
    // Return the body.
    return oauthResponse.getBody();
  }
 
  /**
   * <p>
   * Creates a new user in the database and sends an activation email.
   * </p>
   *
   * <p>
   * For systems that already have their own user activation/management
   * system in place, either remove this function or simply remove the
   * RequestMapping annotation.
   * </p>
   *
   * @param username
   *        The new user's username.
   *       
   * @param password
   *        The new user's password
   *       
   * @param email The new user's email address.
   *
   * @param request
   *        The HTTP request object.
   *
   * @param response
   *        The HTTP response object.
   */
  @RequestMapping(
    value = UserRegistrationRequest.PATH,
    method = RequestMethod.POST)
  public @ResponseBody void registerUser(
    @RequestParam(
      value = User.JSON_KEY_USERNAME,
      required = true)
      final String username,
    @RequestParam(
      value = User.JSON_KEY_PASSWORD,
      required = true)
      final String password,
    @RequestParam(
      value = User.JSON_KEY_EMAIL,
      required = true)
      final String email,
    final HttpServletRequest request,
    final HttpServletResponse response) {
   
    handleRequest(
      request,
      response,
      new UserRegistrationRequest(
        username,
        password,
        email,
        buildRootUrl(request)));
  }
 
  /**
   * <p>
   * Creates a new user in the database and sends an activation email.
   * </p>
   *
   * <p>
   * For systems that already have their own user activation/management
   * system in place, either remove this function or simply remove the
   * RequestMapping annotation.
   * </p>
   *
   * @param username
   *        The new user's username.
   *       
   * @param password
   *        The new user's password
   *       
   * @param email The new user's email address.
   *
   * @param request
   *        The HTTP request object.
   *
   * @param response
   *        The HTTP response object.
   */
  @RequestMapping(
    value = UserActivationRequest.PATH,
    method = RequestMethod.POST)
  public @ResponseBody void activateUser(
    @RequestParam(
      value = User.JSON_KEY_REGISTRATION_KEY,
      required = true)
      final String registrationId,
    final HttpServletRequest request,
    final HttpServletResponse response) {
   
    handleRequest(
      request,
      response,
      new UserActivationRequest(registrationId));
  }
 
  @RequestMapping(value = "testing", method = RequestMethod.GET)
  public @ResponseBody User testing() {
    return UserBin.getInstance().getUser("sink.thaw");
  }
 
  /**
   * If the root of the hierarchy is requested, return the registry, which is
   * a map of all of the schema IDs to their high-level information, e.g.
   * name, description, latest version, etc.
   *
   * @param numToSkip
   *        The number of data points to skip to facilitate paging.
   *
   * @param numToReturn
   *        The number of data points to return to facilitate paging.
   *
   * @param request
   *        The HTTP request object.
   *
   * @param response
   *        The HTTP response object.
   *
   * @return An array of all of the known schemas, limited by paging.
   */
  @RequestMapping(value = { "", "/" }, method = RequestMethod.GET)
  public @ResponseBody MultiValueResult<String> getSchemaIds(
    @RequestParam(
      value = PARAM_PAGING_NUM_TO_SKIP,
      required = false,
      defaultValue = ListRequest.DEFAULT_NUMBER_TO_SKIP_STRING)
      final long numToSkip,
    @RequestParam(
      value = PARAM_PAGING_NUM_TO_RETURN,
      required = false,
      defaultValue = ListRequest.DEFAULT_NUMBER_TO_RETURN_STRING)
      final long numToReturn,
    final HttpServletRequest request,
    final HttpServletResponse response) {
   
    return
      handleRequest(
        request,
        response,
        new SchemaIdsRequest(numToSkip, numToReturn));
  }
 
  /**
   * Creates a request to get the information about the given schema ID, e.g.
   * the name, description, version list, etc.
   *
   * @param schemaId
   *        The schema ID from the URL.
   *
   * @param numToSkip
   *        The number of data points to skip to facilitate paging.
   *
   * @param numToReturn
   *        The number of data points to return to facilitate paging.
   *
   * @param response
   *        The HTTP response object.
   *
   * @return An array of schemas, one for each version of the given schema
   *         ID.
   */
  @RequestMapping(
    value = "{" + PARAM_SCHEMA_ID + "}",
    method = RequestMethod.GET)
  public @ResponseBody MultiValueResult<Long> getSchemaVersions(
    @PathVariable(PARAM_SCHEMA_ID) final String schemaId,
    @RequestParam(
      value = PARAM_PAGING_NUM_TO_SKIP,
      required = false,
      defaultValue = ListRequest.DEFAULT_NUMBER_TO_SKIP_STRING)
      final long numToSkip,
    @RequestParam(
      value = PARAM_PAGING_NUM_TO_RETURN,
      required = false,
      defaultValue = ListRequest.DEFAULT_NUMBER_TO_RETURN_STRING)
      final long numToReturn,
    final HttpServletRequest request,
    final HttpServletResponse response) {
   
    return
      handleRequest(
        request,
        response,
        new SchemaVersionsRequest(schemaId, numToSkip, numToReturn));
  }
 
  /**
   * Creates a request to get the definition of a specific schema ID's
   * version.
   *
   * @param schemaId
   *        The schema ID from the URL.
   *
   * @param version
   *        The schema version from the URL.
   *
   * @param response
   *        The HTTP response object.
   *
   * @return The schema for the given schema ID-version pair.
   */
  @RequestMapping(
    value =
      "{" + PARAM_SCHEMA_ID + "}/" +
      "{" + PARAM_SCHEMA_VERSION + ":[\\d]" + "}",
    method = RequestMethod.GET)
  public @ResponseBody Object getDefinition(
    @PathVariable(PARAM_SCHEMA_ID) final String schemaId,
    @PathVariable(PARAM_SCHEMA_VERSION) final Long version,
    final HttpServletRequest request,
    final HttpServletResponse response) {
   
    return
      handleRequest(
        request,
        response,
        new SchemaRequest(schemaId, version));
  }
 
  /**
   * Retrieves the requested data.
   *
   * @param schemaId
   *        The ID for the schema to which the data pertains. This is part of
   *        the request's path.
   *
   * @param version
   *        The version of the schema to which the data pertains. This is
   *        part of the request's path.
   *
   * @param owner
   *        The user that owns the desired data.
   *
   * @param columnList
   *        The list of columns to return to the user.
   *
   * @param numToSkip
   *        The number of data points to skip to facilitate paging.
   *
   * @param numToReturn
   *        The number of data points to return to facilitate paging.
   *       
   * @param request
   *        The HTTP request object.
   *
   * @param response
   *        The HTTP response object.
   *
   * @return The data as a JSON array of JSON objects where each object
   *         represents a single data point.
   *
   * @see Data
   */
  @RequestMapping(
    value = "{" + PARAM_SCHEMA_ID + "}/{" + PARAM_SCHEMA_VERSION + "}/data",
    method = RequestMethod.GET)
  public @ResponseBody MultiValueResult<Data> getData(
    @PathVariable(PARAM_SCHEMA_ID) final String schemaId,
    @PathVariable(PARAM_SCHEMA_VERSION) final Long version,
    @RequestParam(
      value = PARAM_OWNER,
      required = false)
      final String owner,
    @RequestParam(
      value = PARAM_COLUMN_LIST,
      required = false)
      final List<String> columnList,
    @RequestParam(
      value = PARAM_PAGING_NUM_TO_SKIP,
      required = false,
      defaultValue = ListRequest.DEFAULT_NUMBER_TO_SKIP_STRING)
      final long numToSkip,
    @RequestParam(
      value = PARAM_PAGING_NUM_TO_RETURN,
      required = false,
      defaultValue = ListRequest.DEFAULT_NUMBER_TO_RETURN_STRING)
      final long numToReturn,
    final HttpServletRequest request,
    final HttpServletResponse response) {

    // Handle the request.
    return
      handleRequest(
        request,
        response,
        new DataReadRequest(
          (AuthenticationToken)
            request
              .getAttribute(
                AuthFilter
                  .ATTRIBUTE_AUTHENTICATION_TOKEN),
          (AuthorizationToken)
            request
              .getAttribute(
                AuthFilter
                  .ATTRIBUTE_AUTHORIZATION_TOKEN),
          schemaId,
          version,
          owner,
          columnList,
          numToSkip,
          numToReturn));
  }
 
  /**
   * Writes the requested data.
   *
   * @param schemaId
   *        The ID for the schema to which the data pertains.
   *
   * @param version
   *        The version of the schema to which the data pertains.
   *       
   * @param data
   *        The data to be uploaded, which should be a JSON array of JSON
   *        objects where each object is a single data point.
   *       
   * @param request
   *        The HTTP request object.
   *
   * @param response
   *        The HTTP response object.
   */
  @RequestMapping(
    value = "{" + PARAM_SCHEMA_ID + "}/{" + PARAM_SCHEMA_VERSION + "}/data",
    method = RequestMethod.POST)
  public void putData(
    @PathVariable(PARAM_SCHEMA_ID) final String schemaId,
    @PathVariable(PARAM_SCHEMA_VERSION) final Long version,
    @RequestParam(
      value = PARAM_DATA,
      required = true)
      final String data,
    final HttpServletRequest request,
    final HttpServletResponse response) {
   
    // Make sure the authentication token was a parameter. This prevents
    // malicious code from "hijacking" the token by performing a POST and
    // having the browser inject it as only a cookie.
    Object authenticationTokenIsParam =
      request
        .getAttribute(
          AuthFilter.ATTRIBUTE_AUTHENTICATION_TOKEN_IS_PARAM);
    if(
      (authenticationTokenIsParam == null) ||
      (! ((Boolean) authenticationTokenIsParam))) {
     
      throw
        new OmhException(
          "To upload data, the authentication token is required " +
            "as a parameter.");
    }
   
    // Get the authentication token.
    AuthenticationToken authToken =
      (AuthenticationToken)
        request
          .getAttribute(
            AuthFilter.ATTRIBUTE_AUTHENTICATION_TOKEN);
   
    // Handle the request.
    handleRequest(
      request,
      response,
      new DataWriteRequest(
        authToken,
        schemaId,
        version,
        data));
  }
 
  /**
   * Handles a request then sets the meta-data as HTTP headers and returns
   * the data to be returned to the user.
   *
   * @param httpRequest
   *        The HTTP request.
   *
   * @param httpResponse
   *        The HTTP response.
   *
   * @param request
   *        The already-built, domain-specific request to be serviced.
   *
   * @return The object to be returned to the user.
   */
  private <T> T handleRequest(
    final HttpServletRequest httpRequest,
    final HttpServletResponse httpResponse,
    final Request<? extends T> request) {
   
    // Service the request.
    request.service();
   
    // Retrieve the meta-data and add it as HTTP headers.
    Map<String, Object> metaData = request.getMetaData();
    if(metaData != null) {
      for(String metaDataKey : metaData.keySet()) {
        httpResponse
          .setHeader(
            metaDataKey,
            metaData.get(metaDataKey).toString());
      }
    }
   
    // If this is a list request, add the next and previous parameters.
    if(request instanceof ListRequest) {
      // Create the previous and next headers, if appropriate.
      addNextPreviousHeaders(
        httpRequest,
        httpResponse,
        (ListRequest<?>) request);
    }
   
    // Return the data.
    return request.getData();
  }
 
  /**
   * Builds the base URL for the request that came in. This is everything up
   * to our web applications base, e.g. "http://localhost:8080/omh".
   *
   * @param httpRequest
   *        The original HTTP request.
   *
   * @return The base URL for the request.
   */
  private String buildRootUrl(final HttpServletRequest httpRequest) {
    // It must be a HTTP request.
    StringBuilder builder = new StringBuilder("http");
   
    // If security was used add the "s" to make it "https".
    if(httpRequest.isSecure()) {
      builder.append('s');
    }
   
    // Add the protocol separator.
    builder.append("://");
   
    // Add the name of the server where the request was sent.
    builder.append(httpRequest.getServerName());
   
    // Add the port separator and the port.
    builder.append(':').append(httpRequest.getServerPort());
   
    // Add the context path, e.g. "/omh".
    builder.append(httpRequest.getContextPath());
   
    // Return the root URL.
    return builder.toString();
  }
 
  /**
   * <p>
   * Builds the URL used to make this request based on the request.
   * </p>
   *
   * <p>
   * The URL is built from all of the information Java provides about the
   * system including the hostname. However, in a distributed environment,
   * this may not be adequate or correct.
   * </p>
   *
   * @param httpRequest
   *        The original HTTP request.
   *
   * @return The base URL used to make the request that is calling this
   *         function.
   */
  private String buildRequestUrl(final HttpServletRequest httpRequest) {
    // Start with the root URL.
    StringBuilder builder = new StringBuilder(buildRootUrl(httpRequest));
   
    // Add the specific path in the request.
    builder.append(httpRequest.getPathInfo());

    // Return the base URL, which should only need to have the parameters
    // added to it.
    return builder.toString();
  }
 
  /**
   * Creates and adds the Previous and Next headers.
   *
   * @param httpRequest
   *        The HTTP request.
   *
   * @param httpResponse
   *        The HTTP response.
   *
   * @param listRequest
   *        The ListRequest used to get the paging headers.
   */
  private void addNextPreviousHeaders(
    final HttpServletRequest httpRequest,
    final HttpServletResponse httpResponse,
    final ListRequest<?> listRequest) {
   
    // Get the new set of parameters.
    Map<String, String> parameters =
      listRequest.getPreviousNextParameters();
   
    // If we skipped any data, create a Previous header.
    if(listRequest.getNumToSkip() > 0) {
      // Build the base URL.
      StringBuilder previousBuilder =
        new StringBuilder(buildRequestUrl(httpRequest));
     
      // Add the query separator.
      previousBuilder.append('?');
     
      // Use a try-catch in case our encoding, which is the same for
      // each parameter, is unknown.
      try {
        // Add each of the custom parameters.
        boolean firstPass = true;
        for(String parameterKey : parameters.keySet()) {
          // Add the parameter separator.
          if(firstPass) {
            firstPass = false;
          }
          else {
            previousBuilder.append('&');
          }
         
          // Add the parameter.
          previousBuilder
            .append(
              URLEncoder
                .encode(parameterKey, URL_ENCODING_UTF_8))
            .append('=')
            .append(
              URLEncoder
                .encode(
                  parameters.get(parameterKey),
                  URL_ENCODING_UTF_8));
        }
       
        // Add the paging parameters.
        if(parameters.size() > 0) {
          previousBuilder.append('&');
        }
       
        // Calculate the previous number to skip.
        long previousNumToSkip =
          listRequest.getNumToSkip() -
            listRequest.getNumToReturn();
        // If the previous number to skip is greater than zero, add
        // the number to skip.
        if(previousNumToSkip > 0) {
          previousBuilder
            .append(
              URLEncoder
                .encode(
                  PARAM_PAGING_NUM_TO_SKIP,
                  URL_ENCODING_UTF_8))
            .append('=')
            .append(
              URLEncoder
              .encode(
                Long.toString(previousNumToSkip),
                URL_ENCODING_UTF_8));
          previousBuilder.append('&');
        }

        // Always add the number to return.
        previousBuilder
          .append(
            URLEncoder
              .encode(
                PARAM_PAGING_NUM_TO_RETURN,
                URL_ENCODING_UTF_8))
          .append('=')
          .append(
            URLEncoder
            .encode(
              Long.toString(
                Math.min(
                  listRequest.getNumToSkip(),
                  listRequest.getNumToReturn())),
              URL_ENCODING_UTF_8));
      }
      catch(UnsupportedEncodingException e) {
        LOGGER
          .log(
            Level.SEVERE,
            "The encoding is unknown so the " +
              HEADER_PREVIOUS +
              " header could not be built.");
      }
     
      // Add the previous header.
      httpResponse
        .setHeader(HEADER_PREVIOUS, previousBuilder.toString());
    }
   
    // If the total data-set size is greater than the number of points
    // skipped plus the number of points requested, then there must be more
    // data, and a Next header should be added.
    if(listRequest.getData().count() >
      (listRequest.getNumToSkip() + listRequest.getNumToReturn())) {

      // Build the base URL.
      StringBuilder nextBuilder =
        new StringBuilder(buildRequestUrl(httpRequest));
     
      // Add the query separator.
      nextBuilder.append('?');
     
      // Use a try-catch in case our encoding, which is the same for
      // each parameter, is unknown.
      try {
        // Add each of the custom parameters.
        boolean firstPass = true;
        for(String parameterKey : parameters.keySet()) {
          // Add the parameter separator.
          if(firstPass) {
            firstPass = false;
          }
          else {
            nextBuilder.append('&');
          }
         
          // Add the parameter.
          nextBuilder
            .append(
              URLEncoder
                .encode(parameterKey, URL_ENCODING_UTF_8))
            .append('=')
            .append(
              URLEncoder
                .encode(
                  parameters.get(parameterKey),
                  URL_ENCODING_UTF_8));
        }
       
        // Add the paging parameters.
        if(parameters.size() > 0) {
          nextBuilder.append('&');
        }
       
        // Calculate the previous number to skip.
        long nextNumToSkip =
          listRequest.getNumToSkip() +
            listRequest.getNumToReturn();
        // Always add the number to skip.
        nextBuilder
          .append(
            URLEncoder
              .encode(
                PARAM_PAGING_NUM_TO_SKIP,
                URL_ENCODING_UTF_8))
          .append('=')
          .append(
            URLEncoder
            .encode(
              Long.toString(nextNumToSkip),
              URL_ENCODING_UTF_8));
       
        // Add the parameter separator.
        nextBuilder.append('&');

        // Always add the number to return.
        nextBuilder
          .append(
            URLEncoder
              .encode(
                PARAM_PAGING_NUM_TO_RETURN,
                URL_ENCODING_UTF_8))
          .append('=')
          .append(
            URLEncoder
            .encode(
              Long.toString(listRequest.getNumToReturn()),
              URL_ENCODING_UTF_8));
      }
      catch(UnsupportedEncodingException e) {
        LOGGER
          .log(
            Level.SEVERE,
            "The encoding is unknown so the " +
              HEADER_NEXT +
              " header could not be built.");
      }
     
      // Add the previous header.
      httpResponse
        .setHeader(HEADER_NEXT, nextBuilder.toString());
    }
  }
}
TOP

Related Classes of org.openmhealth.reference.servlet.Version1

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.