com.ibm.caas.CaaSResource.java Source code

Java tutorial

Introduction

Here is the source code for com.ibm.caas.CaaSResource.java

Source

/*
 ********************************************************************
 * Licensed Materials - Property of IBM                             *
 *                                                                  *
 * Copyright IBM Corp. 2015 All rights reserved.                    *
 *                                                                  *
 * US Government Users Restricted Rights - Use, duplication or      *
 * disclosure restricted by GSA ADP Schedule Contract with          *
 * IBM Corp.                                                        *
 *                                                                  *
 * DISCLAIMER OF WARRANTIES. The following [enclosed] code is       *
 * sample code created by IBM Corporation. This sample code is      *
 * not part of any standard or IBM product and is provided to you   *
 * solely for the purpose of assisting you in the development of    *
 * your applications. The code is provided "AS IS", without         *
 * warranty of any kind. IBM shall not be liable for any damages    *
 * arising out of your use of the sample code, even if they have    *
 * been advised of the possibility of such damages.                 *
 ********************************************************************
 */

package com.ibm.caas;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.wink.json4j.JSONArray;
import org.apache.wink.json4j.JSONException;
import org.apache.wink.json4j.JSONObject;

import com.worklight.adapters.rest.api.WLServerAPI;
import com.worklight.adapters.rest.api.WLServerAPIProvider;
import com.worklight.core.auth.OAuthSecurity;

@Path("/")
@OAuthSecurity(enabled = false)
public class CaaSResource {

    public static String CREDENTIALS;
    public static String SERVER;
    public static String TENANT;
    private static List<String> COOKIES;
    private HttpURLConnection urlConnection;
    protected boolean includeTypeInformation = false;

    /*
     * For more info on JAX-RS see
     * https://jsr311.java.net/nonav/releases/1.1/index.html
     */

    // Define logger (Standard java.util.Logger)
    static Logger logger = Logger.getLogger(CaaSResource.class.getName());

    // Define the server api to be able to perform server operations
    WLServerAPI api = WLServerAPIProvider.getWLServerAPI();

    /**
     * Load configuration MACM Configuration
     */

    public void init() {
        String username = WLServerAPIProvider.getWLServerAPI().getConfigurationAPI()
                .getMFPConfigurationProperty("ibm.macm.username");
        String password = WLServerAPIProvider.getWLServerAPI().getConfigurationAPI()
                .getMFPConfigurationProperty("ibm.macm.password");

        CREDENTIALS = "Basic " + new String(Base64.encodeBase64((username + ":" + password).getBytes()));
        SERVER = WLServerAPIProvider.getWLServerAPI().getConfigurationAPI()
                .getMFPConfigurationProperty("ibm.macm.serverurl");

        if (SERVER.startsWith("https")) {
            relaxHostChecking();
        }
    }

    /**
     * Pass throughout CERTs [workaround]
     */
    public void relaxHostChecking() {

        // Override SSL Trust manager without certificate chains validation
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }

            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }

        } };

        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            // Hostname verification. 
            HostnameVerifier allHostsValid = new HostnameVerifier() {
                /**
                 * Verify that the host name is an acceptable match with the server's authentication scheme.
                 * @hostname - the host name
                 * @session - SSLSession used on the connection to host
                 * @return true if the host name is acceptable
                 */
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            };
            // Sets the default HostnameVerifier by all-trusting host verifier.
            HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);

        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (KeyManagementException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * Logs into the CaaS system.
     * 
     * @return {@link Integer} status code -- equal 201 if success
     */
    public Integer connect() throws URISyntaxException, IOException {
        // log message to server log
        logger.info(this.getClass().getSimpleName() + " Connect to MACM INSTANCE / Basic Auth...");
        init();

        try {

            URL url = null;
            if (TENANT == null)

            {
                url = new URL(SERVER + InternalConstants.SERVER_DEFAULT_CONTEXT_ROOT_MY_CONTENTHANDLER + "?"
                        + InternalConstants.CAAS_QUERY_PARAMETER_BASICAUTH);

            } else {
                url = new URL(SERVER + InternalConstants.SERVER_DEFAULT_CONTEXT_ROOT_MY_CONTENTHANDLER + "/"
                        + TENANT + "?" + InternalConstants.CAAS_QUERY_PARAMETER_BASICAUTH);
            }

            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("POST");
            urlConnection.setRequestProperty("Authorization", CREDENTIALS);

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        int statusCode = urlConnection.getResponseCode();

        if (statusCode == 201 || statusCode == 204) {
            setCookies(urlConnection.getHeaderFields().get("Set-Cookie"));
        }
        return statusCode;
    }

    /**
     * Fetch content from MACM instance by doing a HTTP GET request and set
     * Cookies
     * 
     * @param url
     *            {@link String} MACM content url
     * @return {@link InputStream}
     */
    public InputStream openStream(String url) throws IOException {

        urlConnection = (HttpURLConnection) new URL(url).openConnection();
        urlConnection.setRequestMethod("GET");
        urlConnection.setUseCaches(false);

        HttpURLConnection.setFollowRedirects(true);

        if (COOKIES != null) {
            for (String cookie : CaaSResource.COOKIES) {
                urlConnection.addRequestProperty("Cookie", cookie.split(";", 1)[0]);
            }
        }

        if (urlConnection.getResponseCode() == 401 || urlConnection.getResponseCode() == 400) {
            try {
                connect();
            } catch (URISyntaxException e) {
                e.printStackTrace();
            } finally {
                openStream(url);
            }
        }

        return urlConnection.getInputStream();
    }

    /**
     * Returns a list of all content of the given type in the system.
     * 
     * @param tenant
     *            (@link String) Root of the MACM instance
     * @param path
     *            (@link String) contentType - Acceptable values will vary
     *            depending on the types defined by the specific portal. Current
     *            values are: ["Articles", "Biographies", "Catalog",
     *            "Notification", "Offerings", "Plain Texts", "Rich Texts"]
     * @param oid
     *            (@link String) Unique categorie identifier (Ex:
     *            73959b0d-7e87-44d9-b6ca-5ef7343bc545 )
     * @return an {@link Object} list of items and names of fields { names: {
     *         title: 0, id: 1, ... } values: [ ["title", "id value", ... ] }
     * 
     * 
     *         Path for method:
     *         "<server address>/CAAS_Hybrid/adapters/CaaS/items?type={type}&lib={lib-name}"
     *         "<server address>/CAAS_Hybrid/adapters/CaaS/items?oid={oid}"
     * 
     *         for instance
     *         "<server address>/CAAS_Hybrid/adapters/CaaS/items?type=Offer&lib=MACM Default Application"
     * @throws Exception
     */
    @GET
    @Path("/items")
    @Produces(javax.ws.rs.core.MediaType.APPLICATION_JSON)
    public String CAASContentItemsRequest(@QueryParam("tenant") String tenant, @QueryParam("type") String type,
            @QueryParam("oid") String oid, @QueryParam("pageSize") int pageSize,
            @QueryParam("pageNumber") int pageNumber, @QueryParam("categories.all") String categoriesall,
            @QueryParam("keywords.all") String keywordsall, @QueryParam("categories.any") String categoriesany,
            @QueryParam("keywords.any") String keywordsany, @QueryParam("titleFilter") String titleFilter,
            @QueryParam("workflowstatus") String workflowstatus, @QueryParam("sortcriteria") String sortcriteria,
            @QueryParam("property") String propertyKeys, @QueryParam("element") String elementKeys,
            @QueryParam("lib") String libName, @QueryParam("created.since") String csince,
            @QueryParam("created.before") String cbefore, @QueryParam("modified.since") String msince,
            @QueryParam("modified.before") String mbefore)

            throws Exception {
        if (SERVER == null) {
            init();
        }

        String source = SERVER + InternalConstants.SERVER_CONTEXT_ROOT;

        if (tenant != null) {
            TENANT = tenant;
            source += "/" + tenant + "/caas";
        } else {
            source += "/caas";
        }

        String categories = null;
        FilterBy fCategoriesType = null;
        String keywords = null;
        FilterBy fKeywordsType = null;

        if (categoriesall != null) {

            categories = categoriesall;
            fCategoriesType = FilterBy.ALL;

        } else if (categoriesany != null) {
            categories = categoriesany;
            fCategoriesType = FilterBy.ANY;
        } else {
            categories = null;
            fCategoriesType = null;
        }

        if (keywordsall != null) {
            keywords = keywordsall;
            fKeywordsType = FilterBy.ALL;

        } else if (keywordsany != null) {
            keywords = keywordsany;
            fKeywordsType = FilterBy.ANY;
        } else {
            keywords = null;
            fKeywordsType = null;
        }

        if ((oid != null)) {
            source += buildQuery(RequestBy.ID, oid, pageSize, pageNumber, sortcriteria, fCategoriesType, categories,
                    fKeywordsType, keywords, titleFilter, workflowstatus, elementKeys, propertyKeys, libName,
                    csince, cbefore, msince, mbefore);

        } else {
            if (type != null) {
                source += buildQuery(RequestBy.PATH, type, pageSize, pageNumber, sortcriteria, fCategoriesType,
                        categories, fKeywordsType, keywords, titleFilter, workflowstatus, elementKeys, propertyKeys,
                        libName, csince, cbefore, msince, mbefore);
            } else {
                return null;
            }
        }

        return jsonFormatter(IOUtils.toString(openStream(source)), true).toString(4);
    }

    JSONObject jsonFormatter(String content, Boolean isCategorie) throws JSONException {

        JSONObject mData = new JSONObject(content);

        JSONObject header = new JSONObject(mData.opt("header"));
        Map<Integer, String> propertyIndexes, elementIndexes;
        Map<String, Object> itemsObject;
        List<Map<String, Object>> items = new ArrayList<>();

        elementIndexes = parseIndexes(header, "elementIndex");
        propertyIndexes = parseIndexes(header, "propertyIndex");

        JSONArray values = mData.optJSONArray("values");

        for (int i = 0; i < values.length(); i++) {
            JSONArray value = values.getJSONArray(i);

            itemsObject = new HashMap<String, Object>();

            Map<String, Object> propertiesObject = new HashMap<String, Object>();

            for (int j = 0; j < value.length(); j++) {

                if (propertyIndexes.containsKey(j)) {
                    itemsObject.put(propertyIndexes.get(j), value.optString(j));

                } else if (elementIndexes.containsKey(j)) {

                    propertiesObject.put(elementIndexes.get(j), value.optString(j));
                } else {
                    break;
                }

                if (!elementIndexes.isEmpty()) {
                    itemsObject.put("properties", propertiesObject);
                }
            }

            items.add(itemsObject);
        }

        String listofProperties = mData.optString("listProperties");
        JSONObject tmp;
        if (listofProperties == null || listofProperties.isEmpty()) {
            tmp = new JSONObject();
        } else {
            tmp = new JSONObject(listofProperties);
        }
        if (isCategorie) {
            tmp.putOnce("items", new JSONArray(items));
        } else {

            tmp.putOnce("item", new JSONArray(items));
        }

        return tmp;
    }

    Map<Integer, String> parseIndexes(JSONObject header, String indexesName) throws JSONException {
        Map<Integer, String> map = new HashMap<Integer, String>();
        if (header.has(indexesName)) {
            JSONObject json = header.getJSONObject(indexesName);
            @SuppressWarnings("unchecked")
            Iterator<String> it = json.keys();
            while (it.hasNext()) {
                String name = it.next();
                map.put(json.getInt(name), name);
            }
        }
        return map;
    }

    /**
     * Returns a single piece of content
     * 
     * @param tenant
     *            (@link String) Root of the MACM instance
     * @param path
     *            (@link String) Path of requested content - /Offer/Offer 1
     * @param oid
     *            (@link String) ID of requested content
     * @return (@link Object) of requested content { names: { title: 0, id: 1,
     *         ... } values: [ ["title", "id value", ... ] }
     * 
     * 
     *         Path for method:
     *         "<server address>/CAAS_Hybrid/adapters/CaaS/item?path={path}"
     *         "<server address>/CAAS_Hybrid/adapters/CaaS/item?oid={oid}"
     * 
     * @throws Exception
     */

    @GET
    @Path("/item")
    @Produces(javax.ws.rs.core.MediaType.APPLICATION_JSON)
    public String CAASContentItemRequest(@QueryParam("tenant") String tenant, @QueryParam("type") String type,
            @QueryParam("oid") String oid, @QueryParam("lib") String libName) throws Exception {
        if (SERVER == null) {
            init();
        }

        String source = SERVER + InternalConstants.SERVER_CONTEXT_ROOT;

        if (tenant != null) {
            TENANT = tenant;
            source += "/" + tenant + "/caas";
        } else {
            source += "/caas";
        }

        if ((oid != null)) {
            source += buildQuery(RequestBy.ID, oid, 0, 0, null, null, null, null, null, null, null, null, null,
                    null, null, null, null, null);

        } else {

            if (type != null) {
                source += buildQuery(RequestBy.PATH, type, 0, 0, null, null, null, null, null, null, null, null,
                        null, libName, null, null, null, null);
            } else {
                return null;
            }
        }

        return jsonFormatter(IOUtils.toString(openStream(source)), false).toString(4);
    }

    /**
     * Retrieves the asset at the given URL on the CaaS server and returns it in
     * Base64
     * 
     * @param (@link String) assetURL
     * @return (@link Response) base64 encoded image
     * 
     *         Path for method:
     *         "<server address>/CAAS_Hybrid/adapters/CaaS/asset?assetURL={assetURL}"
     */
    @GET
    @Path("/asset")
    public Response CAASAssetRequest(@QueryParam("assetURL") String assetURL)
            throws IOException, URISyntaxException {

        if (SERVER == null) {
            connect();
        }
        return Response.ok(new ByteArrayInputStream(IOUtils.toByteArray(openStream(SERVER + assetURL))),
                urlConnection.getContentType()).build();
    }

    /**
     * Retrieves all openned projects
     * 
     * @return (@link Response) List of open projects
     * @throws Exception
     */
    @GET
    @Path("/projects")
    public String CAASOpenProjectsRequest() throws Exception {

        if (SERVER == null) {
            connect();
        }

        String source = SERVER + InternalConstants.SERVER_CONTEXT_ROOT;
        source += buildQuery(RequestBy.URL, null, 0, 0, null, null, null, null, null, null, null, null, null,
                InternalConstants.CAAS_OPEN_PROJECTS, null, null, null, null);

        return jsonFormatter(IOUtils.toString(openStream(source)), true).toString(4);
    }

    /**
     * Set the Cookies's List
     * 
     * @param cookies
     *            (List<String>) Requested cookies
     */
    public void setCookies(List<String> cookies) {
        CaaSResource.COOKIES = cookies;
    }

    public enum RequestBy {
        /**
         * Indicates a request where the content is obtained by specifiying its
         * content id.
         */
        ID,
        /**
         * Indicates a request where the content is obtained by specifiying its
         * content path.
         */
        PATH,
        /**
         * Indicates a request where the content is obtained by specifiying a
         * URL.
         */
        URL
    }

    public enum CriteriaEnum {
        lastmodifieddate, creationdate, position, title, author, expirydate, publishdate, status
    }

    public enum FilterBy {
        ALL, ANY
    }

    /**
     * The identifier for the content retrived by this request. Its meaning
     * dependens on the type of request: a single content a item, a list of
     * content items or an image URL.
     */

    String buildQuery(RequestBy contentIdentifierType, String contentIdentifier, int pageSize, int pageNumber,
            /* Map<String, Boolean> */String sortCriteria, FilterBy fCategorieType, String categoryFilter,
            FilterBy fKeywordsType, String keywordFilter, String titleFilter, String workflowstatus,
            String elementKeys, String propertyKeys, String libName, String csince, String cbefore, String msince,
            String mbefore) throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append('?').append(encodeParam(InternalConstants.CAAS_QUERY_PARAMETER_MIME_TYPE,
                InternalConstants.CAAS_QUERY_PARAMETER_MIME_TYPE_JSON));
        sb.append('&').append(encodeParam(InternalConstants.CAAS_QUERY_PARAMETER_PAGE,
                InternalConstants.CAAS_QUERY_PARAMETER_PAGE_DEFAULT));
        if (contentIdentifierType == RequestBy.ID) {
            sb.append('&').append(encodeParam(InternalConstants.CAAS_QUERY_PARAMETER_URILE,
                    InternalConstants.CAAS_URILE_BY_ID + contentIdentifier));
        } else if (contentIdentifierType == RequestBy.PATH) {
            sb.append('&').append(
                    encodeParam(InternalConstants.CAAS_QUERY_PARAMETER_URILE, InternalConstants.CAAS_URILE_BY_PATH
                            + libName + InternalConstants.CAAS_CONTENT_TYPE_DEFAULT + contentIdentifier));
        } else {
            sb.append('&')
                    .append(encodeParam(InternalConstants.CAAS_QUERY_PARAMETER_URILE,
                            InternalConstants.CAAS_URILE_BY_PATH + InternalConstants.CAAS_OPEN_PROJECTS))
                    .append('&').append(encodeParam("current", true));
        }

        if (pageSize != 0) {
            sb.append('&').append(encodeParam("ibm.pageSize", pageSize));
        }
        if (pageNumber != 0) {
            sb.append('&').append(encodeParam("ibm.pageNumber", pageNumber));
        }
        if (sortCriteria != null) {
            sb.append('&').append(encodeParam("ibm.sortcriteria", sortCriteria));
        }
        if (categoryFilter != null) {

            if (fCategorieType != null) {
                if (fCategorieType == FilterBy.ALL) {
                    sb.append('&').append(encodeParam("ibm.filter.categories.all", categoryFilter));
                } else {
                    sb.append('&').append(encodeParam("ibm.filter.categories.any", categoryFilter));
                }
            }
        }
        if (keywordFilter != null) {
            if (fKeywordsType != null) {
                if (fKeywordsType == FilterBy.ALL) {
                    sb.append('&').append(

                            encodeParam("ibm.filter.keywords.all", keywordFilter));
                } else {
                    sb.append('&').append(encodeParam("ibm.filter.keywords.any", keywordFilter));
                }
            }
        }
        if (titleFilter != null) {
            sb.append('&').append(encodeParam("ibm.filter.title", titleFilter));
        }
        if (workflowstatus != null) {
            sb.append('&').append(encodeParam("ibm.filter.workflowstatus", workflowstatus));
        }

        if (elementKeys != null) {
            sb.append('&').append(encodeParam("ibm.element.keys", elementKeys));
        }

        if (propertyKeys != null) {
            sb.append('&').append(encodeParam("ibm.property.keys", propertyKeys));
        }

        if (csince != null) {
            sb.append('&').append(encodeParam("ibm.filter.created.since", csince));
        }

        if (cbefore != null) {
            sb.append('&').append(encodeParam("ibm.filter.created.before", cbefore));
        }
        if (msince != null) {
            sb.append('&').append(encodeParam("ibm.filter.modified.since", msince));
        }

        if (mbefore != null) {
            sb.append('&').append(encodeParam("ibm.filter.modified.before", mbefore));
        }

        if (includeTypeInformation) {
            sb.append('&').append(encodeParam("ibm.type.information", "true"));
        }
        return sb.toString();
    }

    String encodeParam(String key, Object value) throws Exception {
        StringBuilder sb = new StringBuilder(URLEncoder.encode(key, InternalConstants.UTF_8).replace("+", "%20"));
        sb.append('=').append(value == null ? ""
                : URLEncoder.encode(value.toString(), InternalConstants.UTF_8).replace("+", "%20"));
        return sb.toString();
    }

    /**
     * This class is package-protected so as not to be exposed to clients.
     */
    static class InternalConstants {

        // Logging stuff
        static final String LOG_ENTRY = " ENTRY ";
        static final String LOG_EXIT = " EXIT ";

        // Android system properties
        static final String PROPERTY_HTTP_AGENT = "http.agent";

        // HTTP connection stuff
        static final String HTTP_SCHEME_HTTP = "http";
        static final String HTTP_SCHEME_HTTPS = "https";
        static final String HTTP_HEADER_USER_AGENT = "User-Agent";
        static final String HTTP_HEADER_ACCEPT_LANGUAGE = "Accept-Language";

        static final String HTTP_HEADER_FIELD_CAAS_RESPONSE = "caas-response";

        // Server connection stuff
        static final String SERVER_DEFAULT_SCHEME = HTTP_SCHEME_HTTP;
        static final String SERVER_DEFAULT_PORT = "80";
        static final String SERVER_DEFAULT_CONTEXT_ROOT = "/wps/mypoc";
        static final String SERVER_CONTEXT_ROOT = "/wps/myportal";
        static final String SERVER_DEFAULT_INSTANCE = "";
        static final String SERVER_DEFAULT_CONTEXT_ROOT_MY_CONTENTHANDLER = "/wps/mycontenthandler";
        static final String SERVER_DEFAULT_PROJECT = "$project";

        static final String SERVER_DEFAULT_QUERY_URI_JSECURITY_CHECK = "!ut/p/model/service-document/j_security_check";
        static final String SERVER_DEFAULT_QUERY_URI_PARAMETER_J_USERNAME = "j_username";
        static final String SERVER_DEFAULT_QUERY_URI_PARAMETER_J_PASSWORD = "j_password";

        // Encoding stuff
        static final String DEFAULT_ENCODING = "UTF-8";

        // CaaS stuff
        static final String CAAS_QUERY_PARAMETER_URILE = "urile";

        static final String CAAS_QUERY_PARAMETER_BASICAUTH = "uri=login:basicauth";

        static final String CAAS_URILE_BY_ID = "wcm:oid:";
        static final String CAAS_URILE_BY_PATH = "wcm:path:";
        static final String CAAS_QUERY_PARAMETER_PAGE = "page";
        static final String CAAS_QUERY_PARAMETER_MIME_TYPE = "mime-type";
        static final String CAAS_QUERY_PARAMETER_PAGE_DEFAULT = "ibm.portal.caas.page";
        static final String CAAS_CONTENT_TYPE_DEFAULT = "/Content Types/";
        static final String CAAS_OPEN_PROJECTS = "MACM System/Views/Open Projects";

        static final String CAAS_QUERY_PARAMETER_MIME_TYPE_JSON = "application/json";
        static final String CAAS_QUERY_PARAMETER_MIME_TYPE_XML = "application/xml";
        static final String CAAS_QUERY_PARAMETER_MIME_TYPE_DEFAULT = CAAS_QUERY_PARAMETER_MIME_TYPE_JSON;

        // Response handling
        static final String CAAS_RESPONSE_JSON_PREFIX_IBM = "ibm.portal.caas";
        static final String CAAS_RESPONSE_JSON_TITLE = "title";
        static final String CAAS_RESPONSE_JSON_ID = "id";
        static final String CAAS_RESPONSE_JSON_AUTHORS = "authors";
        static final String CAAS_RESPONSE_JSON_LAST_MODIFIED = "lastmodifieddate";

        /**
         * The UTF-8 charset string.
         */
        static final String UTF_8 = "utf-8";
    }

    static class Constants {
        /**
         * Default orders.
         */
        public enum Order {
            ASC, DESC;
        }

        public static final String CONTEXT_KEY_ACCEPT_LANGUAGES = "Accept-Language";
        public static final String CONTEXT_VALUE_ACCEPT_LANGUAGES_DEFAULT = "DEFAULT";
        public static final String CONTEXT_KEY_USER_AGENT = "User-Agent";
        public static final String CONTEXT_VALUE_USER_AGENT_DEFAULT = "DEFAULT";
        public static final String CONTEXT_KEY_INDEX_START = "index-start";
        public static final String CONTEXT_KEY_INDEX_COUNT = "index-count";
        public static final String CONTEXT_KEY_ORDER_CRITERIA = "order-criteria";
        public static final String CONTEXT_KEY_ORDER = "order";
        public static final String CONTEXT_VALUE_ORDER_DEFAULT = "DEFAULT"; // equals
        // Order.ASC
        public static final String CONTEXT_KEY_LOCATION = "location";
        public static final String CONTEXT_VALUE_LOCATION_DEFAULT = "DEFAULT"; // equals
        // current
        // location

        public static final String DATA_KEY_ID = "id";
        public static final String DATA_KEY_TITLE = "title";
    }
}