com.microsoft.windowsazure.messaging.Connection.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.windowsazure.messaging.Connection.java

Source

/*
Copyright (c) Microsoft Open Technologies, Inc.
All Rights Reserved
Apache 2.0 License
     
   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.
     
See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
 */

package com.microsoft.windowsazure.messaging;

import static com.microsoft.windowsazure.messaging.Utils.isNullOrWhiteSpace;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.EntityEnclosingRequestWrapper;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.protocol.HTTP;

import android.net.http.AndroidHttpClient;
import android.os.Build;
import android.util.Base64;

/**
 * The connection with a Notification Hub server
 */
class Connection {

    /**
     * Shared access key name
     */
    private static final String SHARED_ACCESS_KEY_NAME = "SharedAccessKeyName";

    /**
     * Shared access key
     */
    private static final String SHARED_ACCESS_KEY = "SharedAccessKey";

    /**
     * Authorization header
     */
    private static final String AUTHORIZATION_HEADER = "Authorization";

    /**
     * UTC timezone
     */
    private static final String UTC_TIME_ZONE = "UTC";

    /**
     * UTF-8 encoding
     */
    private static final String UTF8_ENCODING = "UTF-8";

    /**
     * Endpoint key
     */
    private static final String ENDPOINT_KEY = "Endpoint";

    /**
     * Authentication token expiration minutes
     */
    private static final int EXPIRE_MINUTES = 5;

    /**
     * SDK Version
     */
    private static final String SDK_VERSION = "2014-09";

    /**
     * API version query string parameter
     */
    private static final String API_VERSION_KEY = "api-version";

    /**
     * Api version
     */
    private static final String API_VERSION = "2014-09";

    /**
     * Connection data retrieved from connection string
     */
    private Map<String, String> mConnectionData;

    /**
     * Creates a new connection object
     * @param connectionString   The connection string 
     */
    public Connection(String connectionString) {
        mConnectionData = ConnectionStringParser.parse(connectionString);
    }

    /**
     * Executes a request to the Notification Hub server
     * @param resource   The resource to access
     * @param content   The request content body
     * @param contentType   The request content type
     * @param method   The request method
     * @param extraHeaders   Extra headers to include in the request
     * @return   The response content body
     * @throws Exception
     */
    public String executeRequest(String resource, String content, String contentType, String method,
            Header... extraHeaders) throws Exception {
        return executeRequest(resource, content, contentType, method, null, extraHeaders);
    }

    /**
     * Executes a request to the Notification Hub server
     * @param resource   The resource to access
     * @param content   The request content body
     * @param contentType   The request content type
     * @param method   The request method
     * @param targetHeaderName The header name when we need to get value from it in instead of content
     * @param extraHeaders   Extra headers to include in the request
     * @return   The response content body
     * @throws Exception
     */
    public String executeRequest(String resource, String content, String contentType, String method,
            String targetHeaderName, Header... extraHeaders) throws Exception {
        URI endpointURI = URI.create(mConnectionData.get(ENDPOINT_KEY));
        String scheme = endpointURI.getScheme();

        // Replace the scheme with "https"
        String url = "https" + endpointURI.toString().substring(scheme.length());
        if (!url.endsWith("/")) {
            url += "/";
        }

        url += resource;

        url = AddApiVersionToUrl(url);

        BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest(method, url);

        if (!Utils.isNullOrWhiteSpace(content)) {
            request.setEntity(new StringEntity(content, UTF8_ENCODING));
        }

        request.addHeader(HTTP.CONTENT_TYPE, contentType);
        EntityEnclosingRequestWrapper wrapper = new EntityEnclosingRequestWrapper(request);

        if (extraHeaders != null) {
            for (Header header : extraHeaders) {
                wrapper.addHeader(header);
            }
        }

        return executeRequest(wrapper, targetHeaderName);
    }

    /**
     * Adds the API Version querystring parameter to a URL
     * @param url   The URL to modify
     * @return   The modified URL
     */
    private String AddApiVersionToUrl(String url) {
        URI uri = URI.create(url);

        if (uri.getQuery() == null) {
            url = url + "?";
        } else {
            url = url + "&";
        }

        url = url + API_VERSION_KEY + "=" + API_VERSION;

        return url;
    }

    /**
     * Executes a web request
     * @param request   The request to execute
     * @param targetHeaderName The header name when we need to get value from it in instead of content
     * @return   The content string or header value
     * @throws Exception
     */
    private String executeRequest(HttpUriRequest request, String targetHeaderName) throws Exception {
        addAuthorizationHeader(request);

        int status;
        String content;
        String headerValue = null;
        AndroidHttpClient client = null;
        boolean noHeaderButExpected = false;

        try {
            client = AndroidHttpClient.newInstance(getUserAgent());

            HttpResponse response = client.execute(request);

            status = response.getStatusLine().getStatusCode();
            content = getResponseContent(response);

            if (targetHeaderName != null) {
                if (!response.containsHeader(targetHeaderName)) {
                    noHeaderButExpected = true;
                } else {
                    headerValue = response.getFirstHeader(targetHeaderName).getValue();
                }
            }

        } finally {
            if (client != null) {
                client.close();
            }
        }

        if (status >= 200 && status < 300) {
            if (noHeaderButExpected) {
                throw new NotificationHubException(
                        "The '" + targetHeaderName + "' header does not present in collection", status);
            }
            return targetHeaderName == null ? content : headerValue;
        } else if (status == 404) {
            throw new NotificationHubResourceNotFoundException();
        } else if (status == 401) {
            throw new NotificationHubUnauthorizedException();
        } else if (status == 410) {
            throw new RegistrationGoneException();
        } else {
            throw new NotificationHubException(content, status);
        }
    }

    /**
     * Reads the content from a response to a string
     * @param response   The response to read
     * @return   The content string
     * @throws java.io.IOException
     */
    private String getResponseContent(HttpResponse response) throws IOException {
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            InputStream instream = entity.getContent();
            BufferedReader reader = new BufferedReader(new InputStreamReader(instream));

            StringBuilder sb = new StringBuilder();
            String content = reader.readLine();
            while (content != null) {
                sb.append(content);
                sb.append('\n');
                content = reader.readLine();
            }

            return sb.toString();
        } else {
            return null;
        }
    }

    /**
     * Adds the Authorization header to a request
     * @param request   The request to modify
     * @throws java.security.InvalidKeyException
     */
    private void addAuthorizationHeader(HttpUriRequest request) throws InvalidKeyException {
        String token = generateAuthToken(request.getURI().toString());

        request.addHeader(AUTHORIZATION_HEADER, token);
    }

    /**
     * Generates an AuthToken
     * @param url   The target URL
     * @return   An AuthToken
     * @throws java.security.InvalidKeyException
     */
    private String generateAuthToken(String url) throws InvalidKeyException {

        String keyName = mConnectionData.get(SHARED_ACCESS_KEY_NAME);
        if (isNullOrWhiteSpace(keyName)) {
            throw new AssertionError("SharedAccessKeyName");
        }

        String key = mConnectionData.get(SHARED_ACCESS_KEY);
        if (isNullOrWhiteSpace(key)) {
            throw new AssertionError("SharedAccessKey");
        }

        try {
            url = URLEncoder.encode(url, UTF8_ENCODING).toLowerCase(Locale.ENGLISH);
        } catch (UnsupportedEncodingException e) {
            // this shouldn't happen because of the fixed encoding
        }

        // Set expiration in seconds
        Calendar expireDate = Calendar.getInstance(TimeZone.getTimeZone(UTC_TIME_ZONE));
        expireDate.add(Calendar.MINUTE, EXPIRE_MINUTES);

        long expires = expireDate.getTimeInMillis() / 1000;

        String toSign = url + '\n' + expires;

        // sign

        byte[] bytesToSign = toSign.getBytes();
        Mac mac = null;
        try {
            mac = Mac.getInstance("HmacSHA256");
        } catch (NoSuchAlgorithmException e) {
            // This shouldn't happen because of the fixed algorithm
        }

        SecretKeySpec secret = new SecretKeySpec(key.getBytes(), mac.getAlgorithm());
        mac.init(secret);
        byte[] signedHash = mac.doFinal(bytesToSign);
        String base64Signature = Base64.encodeToString(signedHash, Base64.DEFAULT);
        base64Signature = base64Signature.trim();
        try {
            base64Signature = URLEncoder.encode(base64Signature, UTF8_ENCODING);
        } catch (UnsupportedEncodingException e) {
            // this shouldn't happen because of the fixed encoding
        }

        // construct authorization string
        String token = "SharedAccessSignature sr=" + url + "&sig=" + base64Signature + "&se=" + expires + "&skn="
                + keyName;

        return token;
    }

    /**
     * Generates the User-Agent
     */
    private String getUserAgent() {
        String userAgent = String.format("NOTIFICATIONHUBS/%s (api-origin=%s; os=%s; os_version=%s;)", SDK_VERSION,
                PnsSpecificRegistrationFactory.getInstance().getAPIOrigin(), "Android", Build.VERSION.RELEASE);

        return userAgent;
    }
}