com.comcast.cns.util.Util.java Source code

Java tutorial

Introduction

Here is the source code for com.comcast.cns.util.Util.java

Source

/**
 * Copyright 2012 Comcast Corporation
 * 
 * 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 com.comcast.cns.util;

import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import org.json.JSONWriter;

import com.comcast.cmb.common.util.CMBProperties;
import com.comcast.cns.model.CNSMessage;
import com.comcast.cns.model.CNSMessage.CNSMessageType;
import com.comcast.cns.model.CNSRetryPolicy.CnsBackoffFunction;
import com.comcast.cns.model.CNSSubscription.CnsSubscriptionProtocol;

/**
 * Utility functions for cns
 * @author aseem, bwolf, jorge
 *
 */
public class Util {

    public static final int CNS_USER_TOPIC_LIMIT = 100;

    // redisPubSubRegex matches redis://[password@]hostname:port/channelname
    public static final String redisPubSubRegex = "redis://(([^@]+)@)?([a-z0-9.]+):([0-9]+)/(\\S+)";
    public static final Pattern redisPubSubPattern = Pattern.compile(redisPubSubRegex);

    public static String generateCnsTopicArn(String topicName, String region, String userId) {
        return "arn:cmb:cns:" + region + ":" + userId + ":" + topicName;
    }

    public static String generateCnsTopicSubscriptionArn(String topicArn, CnsSubscriptionProtocol protocol,
            String endpoint) throws NoSuchAlgorithmException {
        MessageDigest m = MessageDigest.getInstance("MD5");
        String name = protocol + ":" + endpoint;
        byte bytes[] = m.digest(name.getBytes());
        return topicArn + ":" + UUID.nameUUIDFromBytes(bytes).toString();
    }

    public static String getNameFromTopicArn(String topicArn) {

        if (topicArn == null) {
            return null;
        }

        String[] elem = topicArn.split(":");

        if (elem.length == 6) {
            return elem[5];
        } else {
            return null;
        }
    }

    /**
     * 
     * @param subArn Of the form <topic-arn>:UUID
     * @return <topic-arn>
     */
    public static String getCnsTopicArn(String subArn) {

        String[] arr = subArn.split(":");

        if (arr.length < 2) {
            throw new IllegalArgumentException(
                    "Bad format for subscription. Expected <topic-arn>:UUID Got:" + subArn);
        }

        StringBuffer sb = new StringBuffer(arr[0]);

        for (int i = 1; i < arr.length - 1; i++) {
            sb.append(":").append(arr[i]);
        }

        return sb.toString();
    }

    private static final Pattern topicPattern = Pattern
            .compile("arn:cmb:cns:[A-Za-z0-9-_]+:[A-Za-z0-9-_]+:[A-Za-z0-9-_]+");

    public static boolean isValidTopicArn(String arn) {

        if (arn == null) {
            return false;
        }

        Matcher matcher = topicPattern.matcher(arn);

        return matcher.matches();
    }

    public static String getUserIdFromTopicArn(String arn) {

        if (!isValidTopicArn(arn)) {
            return null;
        }

        return arn.split(":")[4];
    }

    private static final Pattern subPattern = Pattern
            .compile("arn:cmb:cns:[A-Za-z0-9-_]+:[A-Za-z0-9-_]+:[A-Za-z0-9-_]+:[A-Za-z0-9-_]+");

    public static boolean isValidSubscriptionArn(String arn) {
        Matcher matcher = subPattern.matcher(arn);
        return matcher.matches();
    }

    private static final Pattern topicNamePattern = Pattern.compile("[A-Za-z0-9-_]+");

    public static boolean isValidTopicName(String name) {

        if (name == null || name.equals("") || name.contains(" ") || name.length() > 256) {
            return false;
        }

        Matcher matcher = topicNamePattern.matcher(name);
        return matcher.matches();
    }

    /**
      * Generate the confirmation Json string
      * @param arn The top arn for the topic the user is subscribing to.
      * @param token the token for confirming the subscription
      * @return the Json String 
      */
    public static String generateConfirmationJson(String topicArn, String token, String messageId) {

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Writer writer = new PrintWriter(out);
        JSONWriter jw = new JSONWriter(writer);
        String cnsServiceLocation = CMBProperties.getInstance().getCNSServiceUrl();
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); // UTC, no offset
        Date now = new Date();

        try {
            jw = jw.object();
            jw.key("Type").value(CNSMessageType.SubscriptionConfirmation);
            jw.key("MessageId").value(messageId);
            jw.key("Token").value(token);
            jw.key("TopicArn").value(topicArn);
            jw.key("Message").value("You have chosen to subscribe to the topic " + topicArn
                    + "\\nTo confirm the subscription, visit the SubscribeURL included in this message.");
            jw.key("SubscribeURL").value(
                    cnsServiceLocation + "?Action=ConfirmSubscription&TopicArn=" + topicArn + "&Token=" + token);
            jw.key("Timestamp").value(df.format(now));
            jw.key("SignatureVersion").value("1");
            jw.key("Signature").value("");
            jw.key("SigningCertURL").value("");
            jw.endObject();
            writer.flush();

        } catch (Exception e) {
            return "";
        }

        return out.toString();
    }

    /**
     * Generate the Json message to send to all the endpoints except email
     * @param arn The topic arn for the topic the user is subscribing to.
     * @param message the message to send
     * @param subject the subject to send, also included as the subject in email-json
     * @return the Json String 
     */
    public static String generateMessageJson(CNSMessage cnsMessage, CnsSubscriptionProtocol prot) {

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Writer writer = new PrintWriter(out);
        JSONWriter jw = new JSONWriter(writer);
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); //Time is in UTC zone. i,e no offset
        String timestamp = df.format(cnsMessage.getTimestamp());

        try {
            String message = cnsMessage.getProtocolSpecificMessage(prot);
            jw = jw.object();
            jw.key("Message").value(message);
            jw.key("MessageId").value(cnsMessage.getMessageId());
            jw.key("Signature").value("");
            jw.key("SignatureVersion").value("1");
            jw.key("SigningCertURL").value("");
            jw.key("Subject").value(cnsMessage.getSubject());
            jw.key("Timestamp").value(timestamp);
            jw.key("TopicArn").value(cnsMessage.getTopicArn());
            jw.key("Type").value(cnsMessage.getMessageType().toString());
            String unsubscribeUrl = CMBProperties.getInstance().getCmbUnsubscribeUrl();
            if (unsubscribeUrl.contains("%a")) {
                unsubscribeUrl = unsubscribeUrl.replace("%a", cnsMessage.getSubscriptionArn());
            } else {
                unsubscribeUrl += "?Action=Unsubscribe&SubscriptionArn=" + cnsMessage.getSubscriptionArn();
            }
            jw.key("UnSubscribeURL").value(unsubscribeUrl);
            jw.endObject();
            writer.flush();
        } catch (Exception e) {
            return "";
        }

        return out.toString();
    }

    public static boolean isPhoneNumber(String phone) {

        int size = phone.length();

        for (int i = 0; i < size; i++) {

            Character c = phone.charAt(i);

            if ((c == '-') || (c == '+') || (c == '.') || (c == '(') || (c == ')')) {
                //skip
            } else if ((c.compareTo('0') >= 0) && (c.compareTo('9') <= 0)) {
                //skip
            } else {
                return false;
            }
        }

        return true;
    }

    /**
     * 
     * @param i The number of retry. Must start with 1
     * @param maxBackOffRetries The total number of retries allowed
     * @param minDelayTarget the minimum retry delay in sec
     * @param maxDelayTarget the max retry delay in sec
     * @param backOffFunction which backoff function to return
     * @return the delay in seconds
     */
    public static int getNextRetryDelay(int i, int maxBackOffRetries, int minDelayTarget, int maxDelayTarget,
            CnsBackoffFunction backOffFunction) {
        if (maxBackOffRetries == 0) {
            throw new IllegalArgumentException("maxBackOffRetries cannot be 0");
        }
        double x;
        double a;
        switch (backOffFunction) {
        case linear:
            //equation f(i) = slope*(i-1) + minDelayTarget
            //calculate slope given f(maxBackOffRetries) = maxDelayTarget = slope(maxBackOffRetries - 1) + minDelayTarget
            //=> slope = (maxDelayTarget - minDelayTarget)/ (maxBackOffRetries - 1)
            double slope = (double) (maxDelayTarget - minDelayTarget) / (double) (maxBackOffRetries - 1);
            return (int) (slope * (i - 1) + minDelayTarget);

        case geometric:
            //figure out x using equation: x^(maxBackOffRetries - 1) + minDelayTarget - 1 = maxDelayTarget
            //=> x^(maxBackOffRetries - 1) = maxDelayTarget - minDelayTarget + 1
            //=> x = pow(maxDelayTarget - minDelayTarget + 1, 1/(maxBackOffRetries - 1))
            // and f(i) = x^(i-1) + minDelayTarget - 1

            x = Math.pow(maxDelayTarget - minDelayTarget + 1, 1d / (double) (maxBackOffRetries - 1));
            return (int) Math.pow(x, (i - 1)) + minDelayTarget - 1;

        case exponential:
            //equation to use ae^(i-1) + b - a = y. where b = minDelayTarget
            //=> ae^(maxBackOffRetries -1) + minDelayTarget - a = maxDelayTarget
            //=>a(e^(maxBackOffRetries -1) -1) = maxDelayTarget - minDelayTarget
            //=> a = (maxDelayTarget - minDelayTarget) / (e^(maxBackOffRetries -1) -1)
            a = (maxDelayTarget - minDelayTarget) / (Math.pow(Math.E, maxBackOffRetries - 1) - 1);
            return (int) ((a * Math.pow(Math.E, i - 1)) + minDelayTarget - a);

        case arithmetic:
            //arithmetic is pretty much quadratic for us given euation: ax^2 + b = y
            //f(i) = a(i-1)^2 + b
            //figure out a using b = minDelayTarget & a(maxBackOffRetries-1)^2 + minDelayTarget = maxDelayTarget
            //=> a = (maxDelayTarget-minDelayTarget)/ (maxBackOffRetries-1)^2
            a = (maxDelayTarget - minDelayTarget) / Math.pow(maxBackOffRetries - 1, 2);
            return (int) (a * Math.pow((i - 1), 2) + minDelayTarget);

        default:
            throw new IllegalArgumentException("Unknown backoff" + backOffFunction);
        }
    }
}