com.msopentech.applicationgateway.connection.Router.java Source code

Java tutorial

Introduction

Here is the source code for com.msopentech.applicationgateway.connection.Router.java

Source

/*
 *  Copyright (c) Microsoft Open Technologies
 *  All rights reserved. 
 *  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  
 *  THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT 
 *  LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
 *  MERCHANTABLITY OR NON-INFRINGEMENT. 
 *  See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
 */
package com.msopentech.applicationgateway.connection;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.http.HttpEntity;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.protocol.HTTP;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

import android.app.Activity;
import android.os.AsyncTask;
import android.text.Html;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.widget.TextView;

import com.msopentech.applicationgateway.EnterpriseBrowserActivity;
import com.msopentech.applicationgateway.ApplicationGateway;
import com.msopentech.applicationgateway.R;
import com.msopentech.applicationgateway.data.AgentEntity;
import com.msopentech.applicationgateway.data.ConnectionTraits;
import com.msopentech.applicationgateway.data.Credentials;
import com.msopentech.applicationgateway.preferences.AuthPreferences;
import com.msopentech.applicationgateway.utils.Utility;
import com.msopentech.applicationgateway.utils.XmlUtility;

/**
 * Implements helper class to perform authentication requests.
 */
public class Router {

    /**
     * Exception string used for error detection.
     */
    public static final String EXCEPTION_OCCURRED = "Exception occurred: ";

    /**
     * Response JSON key to get agent id.
     */
    public static String JSON_AGENT_ID_KEY = "agent_id";

    /**
     * Response JSON key to get agent display name.
     */
    public static String JSON_AGENT_DISPLAY_NAME_KEY = "display_name";

    /**
     * Response JSON key to get agents list.
     */
    public static String JSON_AGENTS_KEY = "agents";

    /**
     * Response JSON key to get session id.
     */
    public static String JSON_SESSION_ID_KEY = "session_id";

    /**
     * Token error string.
     */
    private static String ERROR_TOKEN = "Token error - %s";

    /**
     * Session error string.
     */
    private static String ERROR_SESSION = "Session error - %s";

    /**
     * Agent error string.
     */
    private static String ERROR_AGENT = "Agent error - %s";

    /**
     * Action to obtain token.<br/>
     * <b>Input arguments</b>: Object[]{ {@link Credentials} }<br/>
     * <b>Output arguments</b>: Object[]{ {@link String} token, {@link ConnectionTraits} };
     */
    public static final int ACTION_OBTAIN_TOKEN = 0;

    /**
     * Action to obtain agents list.<br/>
     * <b>Input arguments</b>: Object[]{ {@link ConnectionTraits} }<br/>
     * <b>Output arguments</b>: Object[]{ {@link JSONArray}, {@link ConnectionTraits} };
     */
    public static final int ACTION_OBTAIN_AGENTS = 1;

    /**
     * Action to obtain one agent.<br/>
     * <b>Input arguments</b>: Object[]{ {@link ConnectionTraits} }<br/>
     * <b>Output arguments</b>: Object[]{ {@link AgentEntity}, {@link ConnectionTraits} };
     */
    public static final int ACTION_OBTAIN_AGENT = 2;

    /**
     * Action to obtain session ID.<br/>
     * <b>Input arguments</b>: Object[]{ {@link ConnectionTraits} }<br/>
     * <b>Output arguments</b>: Object[]{ {@link String} sessionID, {@link ConnectionTraits} };
     */
    public static final int ACTION_OBTAIN_SESSION = 3;

    /**
     * Action to obtain token.<br/>
     * <b>Input arguments</b>: Object[]{ {@link Credentials} }<br/>
     * <b>Output arguments</b>: Object[]{ {@link ConnectionTraits} };
     */
    public static final int ACTION_AUTHENTICATE = 4;

    /**
     * Template string to be used while composing requests.
     */
    private static String requestTemplate = "<s:Envelope xmlns:s='http://www.w3.org/2003/05/soap-envelope' "
            + "xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' "
            + "xmlns:p='http://schemas.xmlsoap.org/ws/2004/09/policy' "
            + "xmlns:u='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' "
            + "xmlns:a='http://www.w3.org/2005/08/addressing' "
            + "xmlns:wssc='http://schemas.xmlsoap.org/ws/2005/02/sc' "
            + "xmlns:t='http://schemas.xmlsoap.org/ws/2005/02/trust'> " + "<s:Header> " + "<o:Security> "
            + "<u:Timestamp u:Id='_0'> " + "<u:Created>#{created}</u:Created> "
            + "<u:Expires>#{expires}</u:Expires> " + "</u:Timestamp> "
            + "<o:UsernameToken u:Id='uuid-588aa9a6-b538-49c4-9112-625ec501575d-8'> "
            + "<o:Username>#{user}</o:Username> " + "<o:Password>#{pass}</o:Password> " + "</o:UsernameToken> "
            + "</o:Security> " + "</s:Header> " + "<s:Body> " + "<t:RequestSecurityToken> " + "<p:AppliesTo> "
            + "<a:EndpointReference> " + "<a:Address>#{resource}</a:Address> " + "</a:EndpointReference> "
            + "</p:AppliesTo> "
            + "<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType> "
            + "<p:PolicyReference URI='MCMBI'></p:PolicyReference> " + "<o:LoginOptions>3</o:LoginOptions> "
            + "</t:RequestSecurityToken> " + "</s:Body> " + "</s:Envelope>";

    /**
     * Performs <b>asynchronous</b> HTTP request and obtains requested data. If activity argument is not <code>null</code> listener methods
     * will be executed on UI thread that this activity is running on.
     * 
     * @param request Request type.
     * @param arguments Request arguments.
     * @param listener Operation listener.
     * @param activity Activity to get the thread the delegate will run on.
     *
     * @see #ACTION_OBTAIN_TOKEN 
     * @see #ACTION_OBTAIN_AGENTS
     * @see #ACTION_OBTAIN_AGENT 
     * @see #ACTION_OBTAIN_SESSION
     * @see #ACTION_AUTHENTICATE
     */
    public static void performRequest(int request, Object[] arguments, OnOperationExecutionListener listener,
            Activity activity) {
        new AgentsAsyncTask(request, listener, activity).execute(arguments);
    }

    /**
     * Performs HTTP POST request and obtains authentication token.
     * 
     * @param credentials Authentication credentials
     * 
     * @return {@link ConnectionTraits} with valid token OR with an error message if exception is caught or token retrieval failed or
     *         token is <code>null</code> or an empty string. Does NOT return <code>null</code>.
     */
    private static ConnectionTraits obtainToken(Credentials credentials) {
        ConnectionTraits connection = new ConnectionTraits();

        try {
            HttpClient client = new DefaultHttpClient();
            HttpPost request = new HttpPost("https://login.microsoftonline.com/extSTS.srf");

            request.addHeader("SOAPAction", "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue");
            request.addHeader("Content-Type", "application/soap+xml; charset=utf-8");

            Date now = new Date();
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.sssZ");
            String nowAsString = df.format(now);
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(now);
            calendar.add(Calendar.SECOND, 10 * 60);
            Date expires = calendar.getTime();
            String expirationAsString = df.format(expires);

            String message = requestTemplate;

            message = message.replace("#{user}", credentials.getUsername());
            message = message.replace("#{pass}", credentials.getPassword());
            message = message.replace("#{created}", nowAsString);
            message = message.replace("#{expires}", expirationAsString);
            message = message.replace("#{resource}", "appgportal.cloudapp.net");

            StringEntity requestBody = new StringEntity(message, HTTP.UTF_8);
            requestBody.setContentType("text/xml");
            request.setEntity(requestBody);

            BasicHttpResponse response = null;
            response = (BasicHttpResponse) client.execute(request);

            HttpEntity entity = response.getEntity();
            InputStream inputStream = null;
            String token = null;

            inputStream = entity.getContent();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            StringBuffer actualResponse = new StringBuffer();
            while ((line = reader.readLine()) != null) {
                actualResponse.append(line);
                actualResponse.append('\r');
            }
            token = actualResponse.toString();

            String start = "<wst:RequestedSecurityToken>";

            int index = token.indexOf(start);

            if (-1 == index) {
                DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance();
                DocumentBuilder documentBuilder = documentFactory.newDocumentBuilder();
                InputStream errorInputStream = new ByteArrayInputStream(token.getBytes());
                Document currentDoc = documentBuilder.parse(errorInputStream);

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

                String errorReason = null;
                String errorExplained = null;
                Node rootNode = null;
                if ((rootNode = currentDoc.getFirstChild()) != null && /* <S:Envelope> */
                        (rootNode = XmlUtility.getChildNode(rootNode, "S:Body")) != null
                        && (rootNode = XmlUtility.getChildNode(rootNode, "S:Fault")) != null) {
                    Node node = null;
                    if ((node = XmlUtility.getChildNode(rootNode, "S:Reason")) != null) {
                        errorReason = XmlUtility.getChildNodeValue(node, "S:Text");
                    }
                    if ((node = XmlUtility.getChildNode(rootNode, "S:Detail")) != null
                            && (node = XmlUtility.getChildNode(node, "psf:error")) != null
                            && (node = XmlUtility.getChildNode(node, "psf:internalerror")) != null) {
                        errorExplained = XmlUtility.getChildNodeValue(node, "psf:text");
                    }
                }

                if (!TextUtils.isEmpty(errorReason) && !TextUtils.isEmpty(errorExplained)) {
                    logError(null, Router.class.getSimpleName() + ".obtainToken(): " + errorReason + " - "
                            + errorExplained);
                    connection.setError(String.format(ERROR_TOKEN, errorReason + ": " + errorExplained));
                    return connection;
                }
            } else {
                token = token.substring(index);

                String end = "</wst:RequestedSecurityToken>";

                index = token.indexOf(end) + end.length();
                token = token.substring(0, index);

                if (!TextUtils.isEmpty(token)) {
                    connection.setToken(token);
                    return connection;
                }
            }
        } catch (final Exception e) {
            logError(e, Router.class.getSimpleName() + ".obtainToken() Failed");
            return connection.setError(String.format(ERROR_TOKEN, "Token retrieval failed with exception."));
        }
        return connection.setError(String.format(ERROR_TOKEN, "Token retrieval failed."));
    }

    /**
     * Performs HTTP GET request and retrieves proxy agents list. One agent is chosen from it and returned as a result.
     * 
     * @param traits Connection traits.
     * 
     * @return Agent entity. Returns <code>null</code> if exception is caught. Always provides error description if error occurs.
     */
    private static AgentEntity obtainAgent(ConnectionTraits traits) {
        try {
            JSONArray json = obtainAgents(traits);
            if (traits.isError())
                return null;

            traits.agent = new AgentEntity();

            JSONObject item = null;
            String agentId = null;
            String agentDisplayName = null;
            boolean preferredAgentFound = false;
            AgentEntity agent = new AgentEntity();

            AgentEntity preferredAgent = AuthPreferences.loadPreferredAgent();

            for (int i = 0; i < json.length(); i++) {
                try {
                    item = json.getJSONObject(i);
                    agentId = item.getString(JSON_AGENT_ID_KEY);
                    agentDisplayName = item.getString(JSON_AGENT_DISPLAY_NAME_KEY);

                    if (preferredAgent != null && preferredAgent.getAgentId() != null
                            && preferredAgent.getAgentId().contentEquals(agentId)
                            && preferredAgent.getDisplayName() != null
                            && preferredAgent.getDisplayName().contentEquals(agentDisplayName))
                        preferredAgentFound = true;

                    if (i == 0 || preferredAgentFound) {
                        agent = new AgentEntity();
                        agent.setAgentId(agentId);
                        agent.setDisplayName(agentDisplayName);
                        item = new JSONObject();
                        item.put(JSON_AGENT_ID_KEY, agentId);
                        agent.setAgentIdJSON(item.toString());

                        traits.setAgent(agent);

                        if (preferredAgentFound) {
                            if (!agent.getAgentId().isEmpty()) {
                                return agent;
                            } else {
                                traits.setError(String.format(ERROR_AGENT, "Connector ID is empty."));
                            }
                        }
                    }
                } catch (JSONException e) {
                    traits.setError(String.format(ERROR_AGENT, "Connectors list parsing failed."));
                    return null;
                }
            }
            if (agent == null || TextUtils.isEmpty(agent.getAgentId())) {
                traits.setError(String.format(ERROR_AGENT, "Connector ID is null or empty."));
            } else {
                return agent;
            }
        } catch (final Exception e) {
            logError(e, Router.class.getSimpleName() + ".obtainConnectors() Failed");
            traits.setError(String.format(ERROR_AGENT, "Connector retrieval failed."));
        }

        return null;
    }

    /**
     * Performs HTTP GET request and retrieves proxy agents list.
     * 
     * @param traits Connection traits.
     * 
     * @return {@link JSONArray} containing agents list. Returns <code>null</code> if exception is caught. Always provides error description if error occurs.
     */
    private static JSONArray obtainAgents(ConnectionTraits traits) {
        try {
            if (traits == null || TextUtils.isEmpty(traits.token)) {
                String errorText = String.format(ERROR_AGENT,
                        "Traits argument is null or does not contain valid token.");
                if (traits != null) {
                    traits.setError(errorText);
                } else {
                    traits = new ConnectionTraits(errorText);
                }
                return null;
            }

            HttpClient agentsClient = new DefaultHttpClient();
            HttpGet agentsRequest = new HttpGet(
                    EnterpriseBrowserActivity.CLOUD_CONNECTION_HOST_PREFIX + "user/agents");

            agentsRequest.addHeader("X-Bhut-AuthN-Token", traits.token);

            BasicHttpResponse response = null;
            response = (BasicHttpResponse) agentsClient.execute(agentsRequest);

            HttpEntity responseEntity = response.getEntity();
            InputStream responseStream = null;
            JSONObject responseObject = null;
            JSONArray agentsArray = null;

            responseStream = responseEntity.getContent();
            BufferedReader responseReader = new BufferedReader(new InputStreamReader(responseStream));
            String line;
            StringBuffer actualResponse = new StringBuffer();
            while ((line = responseReader.readLine()) != null) {
                actualResponse.append(line);
                actualResponse.append('\r');
            }

            responseObject = new JSONObject(actualResponse.toString());
            agentsArray = responseObject.getJSONArray(JSON_AGENTS_KEY);

            if (agentsArray == null || agentsArray.length() <= 0) {
                traits.setError(String.format(ERROR_AGENT, "No connectors found."));
            } else {
                return agentsArray;
            }
        } catch (final Exception e) {
            logError(e, Router.class.getSimpleName() + ".obtainConnectors() Failed");
            traits.setError(String.format(ERROR_AGENT, "Connectors retrieval failed."));
        }

        return null;
    }

    /**
     * Performs HTTP POST request and obtains session ID.
     * 
     * @param traits Connection traits.
     * @param agentIdJSON Selected proxy agent ID (in JSON string format) that will be used to route through all the requests.
     * 
     * @return Session ID. Returns <code>null</code> if exception is caught. Always provides error description if error occurs.
     */
    private static String obtainSession(ConnectionTraits traits, String agentIdJSON) {
        try {
            if (traits == null || TextUtils.isEmpty(traits.token)) {
                String errorText = String.format(ERROR_SESSION,
                        "Traits argument is null or does not contain valid token.");
                if (traits != null) {
                    traits.setError(errorText);
                } else {
                    traits = new ConnectionTraits(errorText);
                }
                return null;
            }

            HttpClient sessionClient = new DefaultHttpClient();
            HttpPost sessionRequest = new HttpPost(
                    EnterpriseBrowserActivity.CLOUD_CONNECTION_HOST_PREFIX + "user/session");

            sessionRequest.addHeader("x-bhut-authN-token", traits.token);

            StringEntity requestBody = null;
            requestBody = new StringEntity(agentIdJSON);

            requestBody.setContentType("application/json");
            sessionRequest.setEntity(requestBody);

            BasicHttpResponse response = null;
            response = (BasicHttpResponse) sessionClient.execute(sessionRequest);

            HttpEntity responseEntity = response.getEntity();
            InputStream responseStream = null;
            String result = null;

            responseStream = responseEntity.getContent();
            BufferedReader responseReader = new BufferedReader(new InputStreamReader(responseStream));
            String line;
            StringBuffer actualResponse = new StringBuffer();
            while ((line = responseReader.readLine()) != null) {
                actualResponse.append(line);
                actualResponse.append('\r');
            }
            result = actualResponse.toString();

            JSONObject session = null;
            session = new JSONObject(result);
            result = session.getString(JSON_SESSION_ID_KEY);

            if (TextUtils.isEmpty(result)) {
                traits.setError(String.format(ERROR_SESSION, "Session is null or empty."));
            } else {
                traits.setSession(result);
                return result;
            }
        } catch (final Exception e) {
            logError(e, Router.class.getSimpleName() + ".obtainSession() Failed");
            traits.setError(String.format(ERROR_SESSION, "Session retrieval failed."));
        }
        return null;
    }

    /**
     * Logs error to the provided string object.
     * 
     * @param e Exception.
     * @param message Message to add to a default one.
     * 
     * @return Composed error string.
     */
    private static void logError(Exception e, String message) {
        String error = EXCEPTION_OCCURRED + (message == null ? "" : message) + ": "
                + (e == null ? "" : e.toString());
        Log.e(Router.class.getSimpleName(), error);
    }

    /**
     * Implements asynchronous calls to authentication operations.
     */
    private static class AgentsAsyncTask extends AsyncTask<Object, Void, Object> {

        /**
         * Action code, performed by the task.
         */
        private int mOperation;

        /**
         * Operation listener delegate.
         */
        private OnOperationExecutionListener mListener;

        /**
         * Activity to reach the thread to run the delegate on.
         */
        private Activity mActivity;

        /**
         * Default constructor.
         * 
         * @param operation Action type to be processed.
         * @param listener Operation listener.
         * 
         * @see #ACTION_OBTAIN_AGENTS
         * @see #ACTION_OBTAIN_AGENT
         * @see #ACTION_OBTAIN_SESSION
         * @see #ACTION_OBTAIN_TOKEN
         */
        AgentsAsyncTask(int operation, OnOperationExecutionListener listener, Activity activity) {
            super();
            mOperation = operation;
            mListener = listener;
            mActivity = activity;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            if (mActivity == null) {
                mListener.onBeforeExecution(mOperation);
            } else {
                mActivity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mListener.onBeforeExecution(mOperation);
                    }
                });
            }
        }

        @Override
        protected Object doInBackground(Object... arguments) {
            Object result = null;
            try {
                switch (mOperation) {
                case ACTION_OBTAIN_TOKEN: {
                    Credentials credentials = (Credentials) arguments[0];
                    result = Router.obtainToken(credentials);
                    break;
                }
                case ACTION_OBTAIN_AGENTS: {
                    ConnectionTraits traits = (ConnectionTraits) arguments[0];
                    result = new Object[] { Router.obtainAgents(traits), traits };
                    break;
                }
                case ACTION_OBTAIN_AGENT: {
                    ConnectionTraits traits = (ConnectionTraits) arguments[0];
                    result = new Object[] { Router.obtainAgent(traits), traits };
                    break;
                }
                case ACTION_OBTAIN_SESSION: {
                    ConnectionTraits traits = (ConnectionTraits) arguments[0];
                    result = new Object[] { Router.obtainSession(traits, traits.agent.getAgentIdJSON()), traits };
                    break;
                }
                case ACTION_AUTHENTICATE: {
                    try {
                        Credentials credentials = (Credentials) arguments[0];

                        ConnectionTraits traits = Router.obtainToken(credentials);
                        if (traits.isError())
                            return new Object[] { traits };

                        AgentEntity agent = Router.obtainAgent(traits);
                        if (traits.isError())
                            return new Object[] { traits };

                        String session = Router.obtainSession(traits, agent.getAgentIdJSON());
                        if (traits.isError())
                            return new Object[] { traits };

                        traits.sessionID = session;
                        traits.agent = agent;

                        AuthPreferences.storeCredentials(credentials);
                        result = new Object[] { traits };
                    } catch (Exception ex) {
                    }
                    break;
                }
                }
                return result;
            } catch (Exception e) {
                Utility.showAlertDialog(e.toString(), ApplicationGateway.getAppContext());
                return null;
            }
        }

        @Override
        protected void onPostExecute(final Object result) {
            try {
                if (mActivity == null) {
                    mListener.onExecutionComplete(mOperation, (Object[]) result);
                } else {
                    mActivity.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mListener.onExecutionComplete(mOperation, (Object[]) result);
                        }
                    });
                }
            } catch (final Exception e) {
                Utility.showAlertDialog(Router.class.getSimpleName() + ".onPostExecute(): Failed. " + e.toString(),
                        ApplicationGateway.getAppContext());
            }
        }
    }
}