com.pearson.pdn.demos.chainoflearning.EventingUtility.java Source code

Java tutorial

Introduction

Here is the source code for com.pearson.pdn.demos.chainoflearning.EventingUtility.java

Source

/*
* LearningStudio Eventing Sample Application - Chain-of-Learning 
* 
* Need Help or Have Questions? 
* Please use the PDN Developer Community at https://community.pdn.pearson.com
*
* @category   LearningStudio Eventing Sample Application - Chain-of-Learning 
* @author     Wes Williams <wes.williams@pearson.com>
* @author     Pearson Developer Services Team <apisupport@pearson.com>
* @copyright  2015 Pearson Education Inc.
* @license    http://www.apache.org/licenses/LICENSE-2.0  Apache 2.0
* @version    1.0
* 
* 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.
* 
* Portions of this work are reproduced from work created and 
* shared by Google and used according to the terms described in 
* the License. Google is not otherwise affiliated with the 
* development of this work.
*/

package com.pearson.pdn.demos.chainoflearning;

import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.TimeZone;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.macs.CMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Hex;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

/**
 * Utility to handle all Eventing interactions
 */
public class EventingUtility {
    private static final Logger logger = Logger.getLogger(EventingUtility.class);
    private static final ResourceBundle resource = ResourceBundle.getBundle(EventingUtility.class.getName());

    private static final String BASE_URL = resource.getString("eventing.server.url");
    private static final String PRINCIPAL_ID = resource.getString("eventing.principal.id");
    private static final String PRINCIPAL_KEY = resource.getString("eventing.principal.key");
    private static final String CALLBACK_URL = resource.getString("eventing.subscribe.message.callback.url");
    private static final String TAGS = getTagString();

    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
    static {
        DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
    }

    private static HttpClient client;

    static void init() {
        if (client == null) {
            PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
            cm.setDefaultMaxPerRoute(50);
            cm.setMaxTotal(100);
            client = HttpClientBuilder.create().setConnectionManager(cm).build();
        }
    }

    /**
     * Finds an existing subscription to this callback url
     * 
     * @return   Subscription Id
     * @throws Exception   Thrown when operation fails
     */
    static String findSubscription() throws Exception {
        // get current time in UTC for auth token
        String strTime = DATE_FORMAT.format(new Date());
        // use the principal id as the payload for auth token
        String authData = PRINCIPAL_ID;
        // generate the token using the default pipe delimiter
        String authToken = generateAuthToken(strTime, "|", PRINCIPAL_ID, PRINCIPAL_KEY, authData);

        // do a GET on /v1/subscriptions/principal/{principalId}
        HttpGet get = new HttpGet(BASE_URL + "/v1/subscriptions/principal/" + PRINCIPAL_ID);
        // use the auth token in the Authorization header
        get.setHeader("Authorization", authToken);
        HttpResponse response = client.execute(get);

        String responseBody = null;
        HttpEntity responseEntity = response.getEntity();
        if (responseEntity != null) {
            responseBody = new String(EntityUtils.toString(responseEntity));
        }

        // expect a 200 status if the call was executed successfully
        int responseCode = response.getStatusLine().getStatusCode();
        if (responseCode != 200) {
            throw new RuntimeException("Unexpected response code: " + responseCode);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("FOUND SUBS: " + responseBody);
        }
        // {"subscriptions":[
        //     {"subscription":{"id":"","principal_id":"","callback_url":"",
        //      "wsdl_uri":"","queue_name":"","date_created":"","date_cancelled":"",
        //      "tags":[{"tag":{"id":"","type":"","value":""}}]
        //     }
        //   ]
        // }

        // search through the existing subscriptions to find subscription id with our callback url
        JsonParser jsonParser = new JsonParser();
        JsonElement json = jsonParser.parse(responseBody);
        JsonArray subscriptions = json.getAsJsonObject().get("subscriptions").getAsJsonArray();
        for (int i = 0; i < subscriptions.size(); i++) {
            JsonObject subscription = subscriptions.get(i).getAsJsonObject().get("subscription").getAsJsonObject();
            if (CALLBACK_URL.equals(subscription.get("callback_url").getAsString())) {
                return subscription.get("id").getAsString();
            }
        }

        return null;
    }

    /**
     * Creates a subscription for this callback url
     * 
     * @return   ID of the created subscription
     * @throws Exception   Thrown when operation fails
     */
    static String subscribe() throws Exception {
        // get current time in UTC for auth token
        String dateTime = DATE_FORMAT.format(new Date());
        // Include all non-empty values in payload for auth token
        StringBuilder authDataBuilder = new StringBuilder();
        authDataBuilder.append(CALLBACK_URL);
        authDataBuilder.append(TAGS);

        // generate the token using the default pipe delimiter
        String authToken = generateAuthToken(dateTime, "|", PRINCIPAL_ID, PRINCIPAL_KEY,
                authDataBuilder.toString());

        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        nvps.add(new BasicNameValuePair("CALLBACK-URL", CALLBACK_URL));
        nvps.add(new BasicNameValuePair("WSDL-URI", "")); // N/A
        nvps.add(new BasicNameValuePair("TAGS", TAGS));
        // the following parameters were declared in TAGS if needed
        nvps.add(new BasicNameValuePair("CLIENT", ""));
        nvps.add(new BasicNameValuePair("CLIENT-STRING", ""));
        nvps.add(new BasicNameValuePair("MESSAGE-TYPE", ""));
        nvps.add(new BasicNameValuePair("SYSTEM", ""));
        nvps.add(new BasicNameValuePair("SUB-SYSTEM", ""));

        // do a POST on /v1/subscription
        HttpPost post = new HttpPost(BASE_URL + "/v1/subscription");
        // use the auth token in the Authorization header
        post.setHeader("Authorization", authToken);
        // include all parameters for subscription
        post.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
        HttpResponse response = client.execute(post);

        String responseBody = null;
        HttpEntity responseEntity = response.getEntity();
        if (responseEntity != null) {
            responseBody = new String(EntityUtils.toString(responseEntity));
        }

        // expect a 200 status if the call was executed successfully
        int responseCode = response.getStatusLine().getStatusCode();
        if (responseCode != 200) {
            throw new Exception("Unexpected response code of " + responseCode + " and body: " + responseBody + "");
        }

        if (logger.isDebugEnabled()) {
            logger.debug("NEW SUB: " + responseBody);
        }
        // {"subscription":{
        //    "id":"","principal_id":"","callback_url":"","wsdl_uri":"","queue_name":"",
        //    "date_created":"","date_cancelled":"",
        //    "tags":[{"tag":{"id":"","type":"","value":"" }}]
        //   }
        // }

        // find the subscription id
        JsonParser jsonParser = new JsonParser();
        JsonElement json = jsonParser.parse(responseBody);
        JsonObject subscription = json.getAsJsonObject().get("subscription").getAsJsonObject();

        if (subscription == null) {
            throw new Exception("Unable to find subscription id in response body: " + responseBody);
        }

        return subscription.get("id").getAsString();
    }

    /**
     * Deletes the provided subscription
     * 
     * @param subscriptionId   ID of subscription to be deleted
     * @throws Exception   Thrown when operation fails
     */
    static void unsubscribe(String subscriptionId) throws Exception {
        // get current time in UTC for auth token
        String strTime = DATE_FORMAT.format(new Date());
        // use the principal id as the payload for auth token
        String authData = subscriptionId;
        // generate the token using the default pipe delimiter
        String authToken = generateAuthToken(strTime, "|", PRINCIPAL_ID, PRINCIPAL_KEY, authData);

        // do a DELETE on /v1/subscription/{subscriptionId}
        HttpDelete delete = new HttpDelete(BASE_URL + "/v1/subscription/" + subscriptionId);
        delete.setHeader("Authorization", authToken);
        HttpResponse response = client.execute(delete);

        // expect a 200 status if the call was executed successfully
        int responseCode = response.getStatusLine().getStatusCode();
        if (responseCode != 200) {
            throw new Exception("Unexpected repsponse code of " + responseCode);
        }
    }

    /**
     * Verifies an auth token received during delivery is valid.
     * The token would have been signed with our secret, so resign the
     * original data to see if the token matches.
     * 
     * @param token         Auth token to be verify
     * @param delimiter      Delimiter used in the auth token
     * @param payload      The data received with the auth token
     * @throws Exception   Thrown when verification fails
     */
    static void verifyAuthToken(String token, String delimiter, String payload) throws Exception {
        String splitDelimiter = ("|".equals(delimiter)) ? "\\|" : delimiter;

        String[] tokenParts = token.split(splitDelimiter);

        if (tokenParts.length != 3) {
            throw new Exception("Authorization token not 3 parts: " + token);
        }

        String tokenPrincipal = tokenParts[0];
        String tokenTimestamp = tokenParts[1];
        String tokenMac = tokenParts[2];

        if (!PRINCIPAL_ID.equals(tokenPrincipal)) {
            throw new Exception("Principal Mismatch: " + PRINCIPAL_ID + " != " + tokenPrincipal);
        }

        String generatedMac = generateAuthToken(tokenTimestamp, delimiter, PRINCIPAL_ID, PRINCIPAL_KEY, payload);

        if (!generatedMac.equals(token)) {
            throw new Exception("Generated MAC does not match input MAC " + tokenMac);
        }
    }

    /**
     * Creates an auth token signed with principal's credentials
     * 
     * @param dateTime   utc time in yyyy-MM-dd'T'HH:mm:ssZ format 
     * @param delimiter   Delimiter to between token parts
     * @param principalId   Id portion of credentials
     * @param key   Secret portion of credentials
     * @param payload   Data being signed
     * @return   Token value
     * @throws UnsupportedEncodingException
     */
    private static String generateAuthToken(String dateTime, String delimiter, String principalId, String key,
            String payload) throws UnsupportedEncodingException {
        // prepare the data
        byte[] macInput = (dateTime + payload).getBytes("UTF-8");
        byte[] macOutput = new byte[16];
        // sign the data
        CMac macProvider = new CMac(new AESFastEngine(), 128);
        macProvider.init(new KeyParameter(key.getBytes()));
        macProvider.update(macInput, 0, macInput.length);
        macProvider.doFinal(macOutput, 0);
        // hex the signed data
        String macOutputHex = new String(Hex.encode(macOutput));
        // concatenate the token parts
        return principalId + delimiter + dateTime + delimiter + macOutputHex;
    }

    /**
     * Reads tags from properties file and format correctly
     * 
     * @return   Tags in the correct format
     */
    private static String getTagString() {
        Map<String, String> tags = new HashMap<String, String>();
        // loop through properties sequentially
        int tagCount = 0;
        while (true) {
            tagCount++;
            String tagKey = null;
            try {
                tagKey = resource.getString("eventing.subscribe.message.tag" + tagCount + ".key");
            } catch (MissingResourceException e) {
                break;
            }

            String tagValue = resource.getString("eventing.subscribe.message.tag" + tagCount + ".value");
            tags.put(tagKey, tagValue);
        }
        // separate the key and value with colons and the pairs with commas
        String delimiter = "";
        StringBuilder tagsBuilder = new StringBuilder();
        for (String tagKey : tags.keySet()) {
            tagsBuilder.append(delimiter).append(tagKey).append(":").append(tags.get(tagKey));
            delimiter = ",";
        }

        return tagsBuilder.toString();
    }
}