com.github.vseguip.sweet.rest.SugarRestAPI.java Source code

Java tutorial

Introduction

Here is the source code for com.github.vseguip.sweet.rest.SugarRestAPI.java

Source

/********************************************************************\
    
File: SugarRestAPI.java
Copyright 2011 Vicent Segu Pascual
    
This file is part of Sweet.  Sweet is free software: you can
redistribute it and/or modify it under the terms of the GNU General
Public License as published by the Free Software Foundation, either
version 3 of the License, or (at your option) any later version.
    
Sweet is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.  You should have received a copy of the GNU General
Public License along with Sweet. 
    
If not, see http://www.gnu.org/licenses/.  
\********************************************************************/

package com.github.vseguip.sweet.rest;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import com.github.vseguip.sweet.SweetAuthenticatorActivity;
import com.github.vseguip.sweet.contacts.ISweetContact;
import com.github.vseguip.sweet.utils.EasySSLSocketFactory;

import android.content.Context;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;

/*Imeplementa un API para acceder a SugarCRM
 * 
 * Basado en el ejemplo de SampleSyncAdapter.
 * 
 * */
public class SugarRestAPI implements SugarAPI {

    private static final String SUGARCRM_CONTACT_ID_FIELD = "id";

    private static final String SUGARCRM_ACCOUNT_ID_FIELD = "account_id";

    private static final String SUGARCRM_DATE_MODIFIED_FIELD = "date_modified";

    private static final String SUGARCRM_PHONE_WORK_FIELD = "phone_work";

    private static final String SUGARCRM_PHONE_MOBILE_FIELD = "phone_mobile";

    private static final String SUGARCRM_FAX_WORK_FIELD = "phone_fax";

    private static final String SUGARCRM_EMAIL1_FIELD = "email1";

    private static final String SUGARCRM_ACCOUNT_NAME_FIELD = "account_name";

    private static final String SUGARCRM_TITLE_FIELD = "title";

    private static final String SUGARCRM_LAST_NAME_FIELD = "last_name";

    private static final String SUGARCRM_FIRST_NAME_FIELD = "first_name";

    private static final String SUGARCRM_STREET_FIELD = "primary_address_street";
    private static final String SUGARCRM_CITY_FIELD = "primary_address_city";
    private static final String SUGARCRM_STATE_FIELD = "primary_address_state";
    private static final String SUGARCRM_POSTAL_CODE_FIELD = "primary_address_postalcode";
    private static final String SUGARCRM_COUNTRY_FIELD = "primary_address_country";

    private static final int TIMEOUT_OPS = 60 * 1000; // ms

    private static final String KEY_PARAM_RESPONSE_TYPE = "response_type";
    private static final String KEY_PARAM_INPUT_TYPE = "input_type";
    private static final String KEY_PARAM_METHOD = "method";
    private static final String JSON = "JSON";
    private static final String TAG = "SugarRestAPI";
    private static final String LOGIN_METHOD = "login";
    private static final String GET_METHOD = "get_entry_list";
    private static final String SET_METHOD = "set_entries";

    private static final String SUGAR_MODULE_CONTACTS = "Contacts";
    private static final String SUGAR_CONTACTS_QUERY = "";
    private static final String SUGAR_CONTACT_LINK_NAMES = "";
    private static final Object SUGAR_CONTACTS_ORDER_BY = "contacts.date_modified ASC";
    private static URI mServer;
    private static boolean mNoCertValidation;
    private static boolean mEncryptPasswd;

    private HttpClient mHttpClient;

    public SugarRestAPI(String server, boolean noCertValidation, boolean encryptPasswd) throws URISyntaxException {
        setServer(server, noCertValidation, encryptPasswd);
    }

    public boolean validate(String username, String passwd, Context context, Handler handler) {
        return getToken(username, passwd, context, handler) == null;
    }

    public String getToken(String username, String passwd, Context context, Handler handler) {
        final HttpResponse resp;
        JSONObject jso_content = new JSONObject();
        try {
            JSONObject jso_user = new JSONObject();
            String sendpasswd = passwd;
            if (mEncryptPasswd)
                sendpasswd = encryptor(passwd);
            jso_user.put("user_name", username).put("password", sendpasswd);

            jso_content.put("user_auth", jso_user).put("application", "Sweet");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        final HttpPost post = prepareJSONRequest(jso_content.toString(), LOGIN_METHOD);
        HttpClient httpClient = getConnection();
        try {
            resp = httpClient.execute(post);
            if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "Successful authentication");
                }

                String message = getResponseString(resp);
                String authToken;
                JSONObject json = null;
                try {
                    // TODO: Convert JSON to contacts
                    json = (JSONObject) new JSONTokener(message).nextValue();
                    authToken = json.getString("id");
                } catch (JSONException e) {
                    Log.i(TAG, "Error during login" + message);
                    if (json != null) {
                        if (json.has("description")) {
                            try {
                                message = json.getString("description");// get
                                // errot
                                // message!
                            } catch (JSONException ex) {
                                e.printStackTrace();
                                Log.e(TAG, "JSON exception, should never have gotten here!");
                            }
                            ;
                        }
                    }
                    sendResult(false, handler, context, "Error during login " + message);
                    return null;
                }
                sendResult(true, handler, context, authToken);
                return authToken;
            } else {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "Error authenticating" + resp.getStatusLine());
                }
                sendResult(false, handler, context, "Error authenticating" + resp.getStatusLine());
                return null;
            }
        } catch (final IOException e) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "IOException when getting authtoken", e);
            }
            sendResult(false, handler, context, "Error trying to connect to " + mServer.toString());
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            sendResult(false, handler, context,
                    "Error trying to validate your credentials. Check you server name and net connectivity.");
            return null;
        } finally {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "getAuthtoken completing");
            }
        }

    }

    /**
     * @param resp
     * @return
     * @throws IOException
     */
    private String getResponseString(final HttpResponse resp) throws IOException {
        // Buffer the result into a string
        BufferedReader rd = new BufferedReader(new InputStreamReader(resp.getEntity().getContent()), 8192);
        String message = rd.readLine();
        rd.close();
        return message;
    }

    @Override
    /** {@inheritDoc} */
    public List<ISweetContact> getNewerContacts(String token, String date)
            throws IOException, AuthenticationException {
        return getNewerContacts(token, date, 0, 10000);
    }

    @Override
    /** {@inheritDoc} */
    public List<ISweetContact> getNewerContacts(String token, String date, int start, int count)
            throws IOException, AuthenticationException {
        final HttpResponse resp;
        Log.i(TAG, "getNewerContacts()");

        JSONArray jso_array = new JSONArray();
        JSONArray jso_fields = new JSONArray();
        // TODO: add newer fields (adress and other phones)
        jso_fields.put(SUGARCRM_CONTACT_ID_FIELD).put(SUGARCRM_FIRST_NAME_FIELD).put(SUGARCRM_LAST_NAME_FIELD)
                .put(SUGARCRM_TITLE_FIELD).put(SUGARCRM_ACCOUNT_NAME_FIELD).put(SUGARCRM_ACCOUNT_ID_FIELD)
                .put(SUGARCRM_EMAIL1_FIELD).put(SUGARCRM_PHONE_WORK_FIELD).put(SUGARCRM_PHONE_MOBILE_FIELD)
                .put(SUGARCRM_FAX_WORK_FIELD).put(SUGARCRM_STREET_FIELD).put(SUGARCRM_CITY_FIELD)
                .put(SUGARCRM_STATE_FIELD).put(SUGARCRM_POSTAL_CODE_FIELD).put(SUGARCRM_COUNTRY_FIELD)
                .put(SUGARCRM_DATE_MODIFIED_FIELD);
        String sugar_query = SUGAR_CONTACTS_QUERY;
        if (date != null)
            sugar_query = "(contacts.date_modified >= '" + date + "')";
        jso_array.put(token).put(SUGAR_MODULE_CONTACTS).put(sugar_query).put(SUGAR_CONTACTS_ORDER_BY).put(start)
                .put(jso_fields).put(SUGAR_CONTACT_LINK_NAMES).put(count).put(0);

        final HttpPost post = prepareJSONRequest(jso_array.toString(), GET_METHOD);
        HttpClient httpClient = getConnection();
        Log.i(TAG, "Sending request");
        resp = httpClient.execute(post);
        Log.i(TAG, "Got response");
        if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "Successful authentication");
            }
            Log.i(TAG, "Buffering request");
            List<ISweetContact> contacts = new ArrayList<ISweetContact>();
            String message = getResponseString(resp);
            JSONArray result;
            JSONObject json = null;
            try {
                Log.i(TAG, "Parsing response");
                json = (JSONObject) new JSONTokener(message).nextValue();
                result = json.getJSONArray("entry_list");
                Log.i(TAG, "Creating contact objects");
                for (int i = 0; i < result.length(); i++) {
                    try {
                        // ID, first name and last name are compulsory, the rest
                        // can be skipped
                        JSONObject entrada = result.getJSONObject(i).getJSONObject("name_value_list");
                        contacts.add(new SweetContact(
                                getJSONString(entrada.getJSONObject(SUGARCRM_CONTACT_ID_FIELD).getString("value")),
                                getJSONString(entrada.getJSONObject(SUGARCRM_FIRST_NAME_FIELD).getString("value")),
                                getJSONString(entrada.getJSONObject(SUGARCRM_LAST_NAME_FIELD).getString("value")),
                                getSugarValue(entrada, SUGARCRM_TITLE_FIELD, ""),
                                getSugarValue(entrada, SUGARCRM_ACCOUNT_NAME_FIELD, ""),
                                getSugarValue(entrada, SUGARCRM_ACCOUNT_ID_FIELD, ""),
                                getSugarValue(entrada, SUGARCRM_EMAIL1_FIELD, ""),
                                getSugarValue(entrada, SUGARCRM_PHONE_WORK_FIELD, ""),
                                getSugarValue(entrada, SUGARCRM_PHONE_MOBILE_FIELD, ""),
                                getSugarValue(entrada, SUGARCRM_FAX_WORK_FIELD, ""),
                                getSugarValue(entrada, SUGARCRM_STREET_FIELD, ""),
                                getSugarValue(entrada, SUGARCRM_CITY_FIELD, ""),
                                getSugarValue(entrada, SUGARCRM_STATE_FIELD, ""),
                                getSugarValue(entrada, SUGARCRM_POSTAL_CODE_FIELD, ""),
                                getSugarValue(entrada, SUGARCRM_COUNTRY_FIELD, ""),
                                getSugarValue(entrada, SUGARCRM_DATE_MODIFIED_FIELD, "")));
                    } catch (Exception ex) {
                        ex.printStackTrace();
                        Log.e(TAG, "Unknown error parsing, skipping entry");
                    }
                }

                return contacts;
            } catch (Exception e) {
                if (json != null) {
                    Log.i(TAG, "Error parsing json in getNewerContacts. Auth invalid");
                    try {

                        throw new AuthenticationException(json.getString("description"));
                    } catch (JSONException ex) {
                        throw new AuthenticationException("Invalid session");
                    }
                }
            } finally {
                httpClient.getConnectionManager().closeIdleConnections(100, TimeUnit.MILLISECONDS);
            }
        } else {
            Log.v(TAG, "Error authenticating" + resp.getStatusLine());
            throw new AuthenticationException("Invalid session");
        }
        return null;
    }

    @Override
    public List<String> sendNewContacts(String token, List<ISweetContact> contacts, boolean create)
            throws IOException, AuthenticationException {
        final HttpResponse resp;
        ArrayList<String> listaIds = new ArrayList<String>();
        Log.i(TAG, "sendNewContacts()");

        JSONArray jsonData = new JSONArray();
        JSONArray jsonContactList = new JSONArray();
        // TODO: add newer fields (adress and other phones)
        for (ISweetContact c : contacts) {
            JSONArray jsonContactArray = new JSONArray();
            try {
                if (!create)
                    setJsonFieldEntry(jsonContactArray, SUGARCRM_CONTACT_ID_FIELD, c.getId());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_FIRST_NAME_FIELD, c.getFirstName());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_LAST_NAME_FIELD, c.getLastName());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_TITLE_FIELD, c.getTitle());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_ACCOUNT_NAME_FIELD, c.getAccountName());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_ACCOUNT_ID_FIELD, c.getAccountId());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_EMAIL1_FIELD, c.getEmail1());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_PHONE_WORK_FIELD, c.getWorkPhone());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_PHONE_MOBILE_FIELD, c.getMobilePhone());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_FAX_WORK_FIELD, c.getWorkFax());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_STREET_FIELD, c.getStreet());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_CITY_FIELD, c.getCity());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_STATE_FIELD, c.getRegion());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_POSTAL_CODE_FIELD, c.getPostalCode());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_COUNTRY_FIELD, c.getCountry());
                setJsonFieldEntry(jsonContactArray, SUGARCRM_DATE_MODIFIED_FIELD, c.getDateModified());
                jsonContactList.put(jsonContactArray);
            } catch (JSONException e) {
                Log.e(TAG, "Error sending contact to the server");
                e.printStackTrace();
            }

        }
        jsonData.put(token).put(SUGAR_MODULE_CONTACTS).put(jsonContactList);

        final HttpPost post = prepareJSONRequest(jsonData.toString(), SET_METHOD);
        HttpClient httpClient = getConnection();
        Log.i(TAG, "Sending request");

        resp = httpClient.execute(post);
        Log.i(TAG, "Got response");
        if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            Log.i(TAG, "Buffering request");
            String message = getResponseString(resp);
            Log.i(TAG, "Set entries result: " + message);
            JSONObject response = null;
            try {
                response = (JSONObject) new JSONTokener(message).nextValue();
                JSONArray ids = response.getJSONArray("ids");
                for (int i = 0; i < ids.length(); i++) {
                    listaIds.add(ids.getString(i));
                }
            } catch (Exception e) {
                if (response != null) {
                    Log.i(TAG, "Error parsing json in sendNewContacts. Auth invalid");
                    try {
                        throw new AuthenticationException(response.getString("description"));
                    } catch (JSONException ex) {
                        throw new AuthenticationException("Invalid session");
                    }
                }
            }
        }
        return listaIds;
    }

    /**
     * Prepares a JSON entry from a value and a field Name
     * 
     * @param jsonContactArray
     *            The array to put the entry
     * @param fieldName
     *            The SugarCRM field name
     * @param vale
     *            The value of the entry
     * @throws JSONException
     */
    private void setJsonFieldEntry(JSONArray jsonContactArray, String fieldName, String value)
            throws JSONException {
        if ((value != null) && (!TextUtils.isEmpty(value.trim()))) {
            jsonContactArray.put(new JSONObject().put("name", fieldName).put("value", value));
        }
    }

    /**
     * Copies a string from the json object. If using ordinary getString, the
     * array backing the String will be passed as a reference. This means that
     * the String representing the wholse JSON response would be kept in memory
     * causing OOM errors. This is fixed by really copying the substring
     * 
     * @param string 
     *            The string we want to copy
     * @returns An actual copy of the string backed by a different char[]
     * 
     */
    private String getJSONString(String string) {
        char[] copy = new char[string.length()];
        for (int i = 0; i < copy.length; i++) {
            copy[i] = string.charAt(i);
        }
        return new String(copy);
    }

    private String getSugarValue(JSONObject json, String key, String d) {
        String val = d;
        try {
            val = getJSONString(json.getJSONObject(key).getString("value"));

        } catch (JSONException ex) {
            Log.i(TAG, "Field " + key + " not set in SugarCRM, ignoring.");
        }
        return val;
    }

    /**
     * Prepares a JSON request encoding the method and the associated JSON data
     * for a Sugar REST API call and returns an HTTP Post object
     * 
     * 
     * @param rest_data
     *            The string representation of the JSON encoded request and
     *            parameters.
     * @param method
     *            The Sugar REST API method to call.
     * @return The HttpPost representing the Sugar REST Api Call
     * @throws AssertionError
     */
    private HttpPost prepareJSONRequest(String rest_data, String method) throws AssertionError {
        AbstractHttpEntity entity = null;
        try {
            final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
            params.add(new BasicNameValuePair(KEY_PARAM_METHOD, method));
            params.add(new BasicNameValuePair(KEY_PARAM_INPUT_TYPE, JSON));
            params.add(new BasicNameValuePair(KEY_PARAM_RESPONSE_TYPE, JSON));
            params.add(new BasicNameValuePair("rest_data", rest_data));
            entity = new UrlEncodedFormEntity(params, HTTP.UTF_8);
            final HttpPost post = new HttpPost(mServer);
            post.addHeader(entity.getContentType());
            post.setEntity(entity);
            return post;

        } catch (final UnsupportedEncodingException e) {
            // this should never happen.
            throw new AssertionError(e);
        }
    }

    /**
     * Get an HttpConnection object. Will create a new one if needed.
     * 
     * @return A defualt HTTP connection object
     */
    private HttpClient getConnection() {
        if (mHttpClient == null) {
            // registers schemes for both http and https
            HttpParams params = new BasicHttpParams();
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
            HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
            HttpProtocolParams.setUseExpectContinue(params, false);
            HttpConnectionParams.setConnectionTimeout(params, TIMEOUT_OPS);
            HttpConnectionParams.setSoTimeout(params, TIMEOUT_OPS);
            ConnManagerParams.setTimeout(params, TIMEOUT_OPS);
            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            final SSLSocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory();
            sslSocketFactory.setHostnameVerifier(SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            if (!mNoCertValidation)
                registry.register(new Scheme("https", sslSocketFactory, 443));
            else
                registry.register(new Scheme("https", new EasySSLSocketFactory(), 443));
            ThreadSafeClientConnManager manager = new ThreadSafeClientConnManager(params, registry);
            mHttpClient = new DefaultHttpClient(manager, params);

        }
        return mHttpClient;
    }

    /**
     * Sends the authentication response from server back to the caller main UI
     * thread through its handler.
     * 
     * @param result
     *            The boolean holding authentication result
     * @param handler
     *            The main UI thread's handler instance.
     * @param context
     *            The caller Activity's context.
     * @param message
     *            SessionID if login was successful or error message if it was
     *            not.
     */
    private static void sendResult(final Boolean result, final Handler handler, final Context context,
            final String message) {
        if (handler == null || context == null) {
            return;
        }
        handler.post(new Runnable() {
            public void run() {
                ((SweetAuthenticatorActivity) context).onValidationResult(result, message);
            }
        });
    }

    private String encryptor(String password) {
        String pwd = password;

        String temppass = null;

        byte[] defaultBytes = pwd.getBytes();
        try {
            MessageDigest algorithm = MessageDigest.getInstance("MD5");
            algorithm.reset();
            algorithm.update(defaultBytes);
            byte messageDigest[] = algorithm.digest();

            StringBuffer hexString = new StringBuffer();
            for (int i = 0; i < messageDigest.length; i++) {
                hexString.append(String.format("%02x", 0xFF & messageDigest[i]));
            }
            temppass = hexString.toString();
        } catch (NoSuchAlgorithmException nsae) {
            System.out.println("No Such Algorithm found");
        }

        return temppass;
    }

    @Override
    public void setServer(String server, boolean noCertValidation, boolean encryptPasswd)
            throws URISyntaxException {
        mServer = new URI(server);
        mNoCertValidation = noCertValidation;
        mEncryptPasswd = encryptPasswd;
    }

}